@efectoapp/mcp-server 0.1.18 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/dist/tools/discovery.d.ts.map +1 -1
- package/dist/tools/discovery.js +128 -1
- package/dist/tools/discovery.js.map +1 -1
- package/dist/tools/images.d.ts.map +1 -1
- package/dist/tools/images.js +102 -0
- package/dist/tools/images.js.map +1 -1
- package/dist/tools/output.d.ts.map +1 -1
- package/dist/tools/output.js +3 -6
- package/dist/tools/output.js.map +1 -1
- package/dist/tools/state.d.ts +33 -1
- package/dist/tools/state.d.ts.map +1 -1
- package/dist/tools/state.js +552 -18
- package/dist/tools/state.js.map +1 -1
- package/package.json +1 -1
package/dist/tools/state.js
CHANGED
|
@@ -85,7 +85,7 @@ function getDefaultAnimationParams(type) {
|
|
|
85
85
|
const base = {
|
|
86
86
|
type,
|
|
87
87
|
enabled: true,
|
|
88
|
-
speed:
|
|
88
|
+
speed: 0.2,
|
|
89
89
|
amplitude: 50,
|
|
90
90
|
staggerDelay: 150,
|
|
91
91
|
staggerMode: 'sequential',
|
|
@@ -289,8 +289,118 @@ function createDefaultBackgroundLayer(backgroundColor) {
|
|
|
289
289
|
},
|
|
290
290
|
contentType: 'solid',
|
|
291
291
|
solidColor: backgroundColor,
|
|
292
|
+
fill: {
|
|
293
|
+
type: 'solid',
|
|
294
|
+
color: backgroundColor,
|
|
295
|
+
opacity: 1,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const DEFAULT_LINEAR_GRADIENT_FILL = {
|
|
300
|
+
type: 'linear',
|
|
301
|
+
angle: 135,
|
|
302
|
+
stops: [
|
|
303
|
+
{ color: '#7c3aed', opacity: 1, position: 0 },
|
|
304
|
+
{ color: '#22d3ee', opacity: 1, position: 1 },
|
|
305
|
+
],
|
|
306
|
+
};
|
|
307
|
+
const DEFAULT_RADIAL_GRADIENT_FILL = {
|
|
308
|
+
type: 'radial',
|
|
309
|
+
center: { x: 0.5, y: 0.5 },
|
|
310
|
+
radius: 0.8,
|
|
311
|
+
stops: [
|
|
312
|
+
{ color: '#a78bfa', opacity: 1, position: 0 },
|
|
313
|
+
{ color: '#0f172a', opacity: 1, position: 1 },
|
|
314
|
+
],
|
|
315
|
+
};
|
|
316
|
+
const MIN_GRADIENT_STOPS = 2;
|
|
317
|
+
const MAX_GRADIENT_STOPS = 8;
|
|
318
|
+
function clamp01(value) {
|
|
319
|
+
if (!Number.isFinite(value))
|
|
320
|
+
return 0;
|
|
321
|
+
return Math.min(1, Math.max(0, value));
|
|
322
|
+
}
|
|
323
|
+
function normalizeAngle(value) {
|
|
324
|
+
if (!Number.isFinite(value))
|
|
325
|
+
return 0;
|
|
326
|
+
const normalized = value % 360;
|
|
327
|
+
return normalized < 0 ? normalized + 360 : normalized;
|
|
328
|
+
}
|
|
329
|
+
function clampRadius(value) {
|
|
330
|
+
if (!Number.isFinite(value))
|
|
331
|
+
return DEFAULT_RADIAL_GRADIENT_FILL.radius;
|
|
332
|
+
return Math.min(2, Math.max(0.05, value));
|
|
333
|
+
}
|
|
334
|
+
function isHexColor(value) {
|
|
335
|
+
return /^#[0-9a-fA-F]{3}$/.test(value) || /^#[0-9a-fA-F]{6}$/.test(value);
|
|
336
|
+
}
|
|
337
|
+
function normalizeGradientStop(stop, fallback) {
|
|
338
|
+
return {
|
|
339
|
+
color: isHexColor(stop.color) ? stop.color : fallback.color,
|
|
340
|
+
opacity: clamp01(stop.opacity),
|
|
341
|
+
position: clamp01(stop.position),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function normalizeGradientStops(stops, fallbackStops) {
|
|
345
|
+
const fallbackStart = fallbackStops[0];
|
|
346
|
+
const fallbackEnd = fallbackStops[fallbackStops.length - 1] ?? fallbackStart;
|
|
347
|
+
const input = Array.isArray(stops) ? stops : [];
|
|
348
|
+
const normalized = input
|
|
349
|
+
.map((stop, index) => normalizeGradientStop(stop, fallbackStops[Math.min(index, fallbackStops.length - 1)] ?? fallbackEnd))
|
|
350
|
+
.slice(0, MAX_GRADIENT_STOPS);
|
|
351
|
+
if (normalized.length < MIN_GRADIENT_STOPS) {
|
|
352
|
+
if (normalized.length === 0) {
|
|
353
|
+
normalized.push(normalizeGradientStop(fallbackStart, fallbackStart), normalizeGradientStop(fallbackEnd, fallbackEnd));
|
|
354
|
+
}
|
|
355
|
+
else if (normalized.length === 1) {
|
|
356
|
+
normalized.push(normalizeGradientStop(fallbackEnd, fallbackEnd));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return normalized
|
|
360
|
+
.map((stop, index) => ({ stop, index }))
|
|
361
|
+
.sort((a, b) => a.stop.position - b.stop.position || a.index - b.index)
|
|
362
|
+
.map(({ stop }) => stop);
|
|
363
|
+
}
|
|
364
|
+
function normalizeLinearGradientFill(fill) {
|
|
365
|
+
const fallback = DEFAULT_LINEAR_GRADIENT_FILL;
|
|
366
|
+
return {
|
|
367
|
+
type: 'linear',
|
|
368
|
+
angle: normalizeAngle(fill.angle),
|
|
369
|
+
stops: normalizeGradientStops(fill.stops, fallback.stops),
|
|
292
370
|
};
|
|
293
371
|
}
|
|
372
|
+
function normalizeRadialGradientFill(fill) {
|
|
373
|
+
const fallback = DEFAULT_RADIAL_GRADIENT_FILL;
|
|
374
|
+
return {
|
|
375
|
+
type: 'radial',
|
|
376
|
+
center: {
|
|
377
|
+
x: clamp01(fill.center?.x ?? fallback.center.x),
|
|
378
|
+
y: clamp01(fill.center?.y ?? fallback.center.y),
|
|
379
|
+
},
|
|
380
|
+
radius: clampRadius(fill.radius),
|
|
381
|
+
stops: normalizeGradientStops(fill.stops, fallback.stops),
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function parseGradientStopsInput(input, fallbackStops) {
|
|
385
|
+
if (!Array.isArray(input))
|
|
386
|
+
return null;
|
|
387
|
+
const parsed = input
|
|
388
|
+
.map((item, index) => {
|
|
389
|
+
if (!item || typeof item !== 'object')
|
|
390
|
+
return null;
|
|
391
|
+
const candidate = item;
|
|
392
|
+
const fallback = fallbackStops[Math.min(index, fallbackStops.length - 1)] ?? fallbackStops[0];
|
|
393
|
+
const opacityRaw = typeof candidate.opacity === 'number' ? candidate.opacity : Number.parseFloat(String(candidate.opacity ?? ''));
|
|
394
|
+
const positionRaw = typeof candidate.position === 'number' ? candidate.position : Number.parseFloat(String(candidate.position ?? ''));
|
|
395
|
+
return {
|
|
396
|
+
color: typeof candidate.color === 'string' && isHexColor(candidate.color) ? candidate.color : fallback.color,
|
|
397
|
+
opacity: clamp01(Number.isFinite(opacityRaw) ? opacityRaw : fallback.opacity),
|
|
398
|
+
position: clamp01(Number.isFinite(positionRaw) ? positionRaw : fallback.position),
|
|
399
|
+
};
|
|
400
|
+
})
|
|
401
|
+
.filter((stop) => stop !== null);
|
|
402
|
+
return parsed.length >= MIN_GRADIENT_STOPS ? parsed : null;
|
|
403
|
+
}
|
|
294
404
|
// Tool definitions
|
|
295
405
|
exports.stateTools = [
|
|
296
406
|
{
|
|
@@ -301,7 +411,7 @@ exports.stateTools = [
|
|
|
301
411
|
properties: {
|
|
302
412
|
aspectRatio: {
|
|
303
413
|
type: 'string',
|
|
304
|
-
enum: ['16:9', '9:16', '1:1', '4:3', 'full'],
|
|
414
|
+
enum: ['16:9', '9:16', '1:1', '4:3', '3:4', 'full'],
|
|
305
415
|
description: 'Canvas aspect ratio (default: 9:16 for portrait poster)',
|
|
306
416
|
default: '9:16',
|
|
307
417
|
},
|
|
@@ -316,13 +426,13 @@ exports.stateTools = [
|
|
|
316
426
|
},
|
|
317
427
|
{
|
|
318
428
|
name: 'set_background',
|
|
319
|
-
description: 'Configure the background layer (always at index 0). Can be solid
|
|
429
|
+
description: 'Configure the background layer (always at index 0). Can be solid fill, linear/radial gradient fill, image, or video.',
|
|
320
430
|
inputSchema: {
|
|
321
431
|
type: 'object',
|
|
322
432
|
properties: {
|
|
323
433
|
type: {
|
|
324
434
|
type: 'string',
|
|
325
|
-
enum: ['solid', 'image', 'video'],
|
|
435
|
+
enum: ['solid', 'gradient', 'image', 'video'],
|
|
326
436
|
description: 'Background content type',
|
|
327
437
|
default: 'solid',
|
|
328
438
|
},
|
|
@@ -330,6 +440,50 @@ exports.stateTools = [
|
|
|
330
440
|
type: 'string',
|
|
331
441
|
description: 'Solid background color in hex (for type: solid)',
|
|
332
442
|
},
|
|
443
|
+
gradientStartColor: {
|
|
444
|
+
type: 'string',
|
|
445
|
+
description: 'Gradient start color in hex (for type: gradient)',
|
|
446
|
+
},
|
|
447
|
+
gradientEndColor: {
|
|
448
|
+
type: 'string',
|
|
449
|
+
description: 'Gradient end color in hex (for type: gradient)',
|
|
450
|
+
},
|
|
451
|
+
gradientStops: {
|
|
452
|
+
type: 'array',
|
|
453
|
+
description: 'Optional gradient stops array (2-8 items). If provided, overrides start/end colors.',
|
|
454
|
+
items: {
|
|
455
|
+
type: 'object',
|
|
456
|
+
properties: {
|
|
457
|
+
color: { type: 'string', description: 'Stop color in hex' },
|
|
458
|
+
opacity: { type: 'number', description: 'Stop opacity (0-1)' },
|
|
459
|
+
position: { type: 'number', description: 'Stop position (0-1)' },
|
|
460
|
+
},
|
|
461
|
+
required: ['color', 'position'],
|
|
462
|
+
},
|
|
463
|
+
minItems: 2,
|
|
464
|
+
maxItems: 8,
|
|
465
|
+
},
|
|
466
|
+
gradientStyle: {
|
|
467
|
+
type: 'string',
|
|
468
|
+
enum: ['linear', 'radial'],
|
|
469
|
+
description: 'Gradient style (for type: gradient)',
|
|
470
|
+
},
|
|
471
|
+
gradientAngle: {
|
|
472
|
+
type: 'number',
|
|
473
|
+
description: 'Linear gradient angle in degrees (for linear style)',
|
|
474
|
+
},
|
|
475
|
+
gradientCenterX: {
|
|
476
|
+
type: 'number',
|
|
477
|
+
description: 'Radial center X (0-1, for radial style)',
|
|
478
|
+
},
|
|
479
|
+
gradientCenterY: {
|
|
480
|
+
type: 'number',
|
|
481
|
+
description: 'Radial center Y (0-1, for radial style)',
|
|
482
|
+
},
|
|
483
|
+
gradientRadius: {
|
|
484
|
+
type: 'number',
|
|
485
|
+
description: 'Radial radius scale (for radial style)',
|
|
486
|
+
},
|
|
333
487
|
imageUrl: {
|
|
334
488
|
type: 'string',
|
|
335
489
|
description: 'Image URL (for type: image)',
|
|
@@ -369,13 +523,19 @@ exports.stateTools = [
|
|
|
369
523
|
opacity: { type: 'number', description: 'Opacity 0-1', default: 1 },
|
|
370
524
|
// Text layer properties
|
|
371
525
|
content: { type: 'string', description: 'Text content (required for text layers)' },
|
|
526
|
+
textType: { type: 'string', enum: ['point', 'frame'], description: 'Text authoring mode: "frame" for layout copy with wrapping (recommended), "point" for scalable text', default: 'frame' },
|
|
372
527
|
fontFamily: { type: 'string', description: 'Font family name', default: 'DM Sans' },
|
|
373
528
|
fontSize: { type: 'number', description: 'Font size in pixels', default: 72 },
|
|
374
529
|
fontWeight: { type: 'string', enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], description: 'Font weight', default: 'bold' },
|
|
375
530
|
color: { type: 'string', description: 'Text color in hex', default: '#ffffff' },
|
|
376
531
|
textAlign: { type: 'string', enum: ['left', 'center', 'right'], description: 'Text alignment', default: 'center' },
|
|
377
|
-
letterSpacing: { type: 'number', description: 'Letter spacing in pixels', default: 0 },
|
|
378
|
-
lineHeight: { type: 'number', description: 'Line height multiplier', default: 1.2 },
|
|
532
|
+
letterSpacing: { type: 'number', description: 'Letter spacing in pixels (negative for tighter, e.g., -1 to -2 for headlines)', default: 0 },
|
|
533
|
+
lineHeight: { type: 'number', description: 'Line height multiplier (0.9-1.0 for tight headlines, 1.3-1.5 for body)', default: 1.2 },
|
|
534
|
+
textTransformMode: { type: 'string', enum: ['scale', 'box'], description: 'Text sizing mode: "box" for natural wrapping (recommended), "scale" to stretch to fit', default: 'box' },
|
|
535
|
+
textBoxWidth: { type: 'number', description: 'Width in pixels for text wrapping when using box mode (e.g., 300-600 for readable lines)' },
|
|
536
|
+
frameWidthPx: { type: 'number', description: 'Frame width in CSS pixels for frame text (resizing changes this, not fontSize)' },
|
|
537
|
+
frameHeightPx: { type: 'number', description: 'Frame height in CSS pixels for frame text' },
|
|
538
|
+
autoSize: { type: 'string', enum: ['none', 'height', 'widthAndHeight'], description: 'Auto-size mode for frame text' },
|
|
379
539
|
// Text animation properties (use list_text_animations to see all options)
|
|
380
540
|
animationType: {
|
|
381
541
|
type: 'string',
|
|
@@ -453,9 +613,72 @@ exports.stateTools = [
|
|
|
453
613
|
required: ['type'],
|
|
454
614
|
},
|
|
455
615
|
},
|
|
616
|
+
{
|
|
617
|
+
name: 'add_group',
|
|
618
|
+
description: 'Add a flexbox container with child layers. Works like CSS flexbox: set direction (column/row), alignment, and gap. Children are positioned automatically by the layout engine — do NOT set child x/y. Use this for ALL text layout (heading + subheading, title + date, etc.).',
|
|
619
|
+
inputSchema: {
|
|
620
|
+
type: 'object',
|
|
621
|
+
properties: {
|
|
622
|
+
name: { type: 'string', description: 'Group name' },
|
|
623
|
+
layoutMode: { type: 'string', enum: ['row', 'column'], description: 'Flex direction: "column" stacks vertically (default, most common), "row" places side by side', default: 'column' },
|
|
624
|
+
widthMode: { type: 'string', enum: ['hug', 'fixed'], description: 'Horizontal sizing mode: hug contents or fixed width' },
|
|
625
|
+
heightMode: { type: 'string', enum: ['hug', 'fixed'], description: 'Vertical sizing mode: hug contents or fixed height' },
|
|
626
|
+
gap: { type: 'number', description: 'Space between children (like CSS gap). 0.02=small, 0.04=medium, 0.06=large', default: 0.02 },
|
|
627
|
+
justifyContent: { type: 'string', enum: ['start', 'center', 'end', 'space-between', 'space-around', 'space-evenly'], description: 'Main-axis alignment (like CSS justify-content)', default: 'start' },
|
|
628
|
+
alignItems: { type: 'string', enum: ['start', 'center', 'end'], description: 'Cross-axis alignment (like CSS align-items): "start"=left, "center"=centered, "end"=right', default: 'center' },
|
|
629
|
+
wrap: { type: 'boolean', description: 'Wrap children to next line on overflow (like CSS flex-wrap)', default: false },
|
|
630
|
+
padding: { type: 'number', description: 'Inner padding (like CSS padding)', default: 0 },
|
|
631
|
+
paddingTop: { type: 'number', description: 'Top padding override' },
|
|
632
|
+
paddingRight: { type: 'number', description: 'Right padding override' },
|
|
633
|
+
paddingBottom: { type: 'number', description: 'Bottom padding override' },
|
|
634
|
+
paddingLeft: { type: 'number', description: 'Left padding override' },
|
|
635
|
+
fillColor: { type: 'string', description: 'Group background color (hex)' },
|
|
636
|
+
fillOpacity: { type: 'number', description: 'Group background opacity (0-1)' },
|
|
637
|
+
cornerRadius: { type: 'number', description: 'Corner radius' },
|
|
638
|
+
x: { type: 'number', description: 'Group X position (-1 to 1, 0 is center)', default: 0 },
|
|
639
|
+
y: { type: 'number', description: 'Group Y position (-1 to 1, POSITIVE is UP/TOP). 0.15=upper, 0=center, -0.3=lower', default: 0 },
|
|
640
|
+
width: { type: 'number', description: 'Group width (0-1 relative to canvas)', default: 0.6 },
|
|
641
|
+
height: { type: 'number', description: 'Group height (0-1 relative to canvas)', default: 0.4 },
|
|
642
|
+
rotation: { type: 'number', description: 'Rotation in degrees', default: 0 },
|
|
643
|
+
opacity: { type: 'number', description: 'Opacity 0-1', default: 1 },
|
|
644
|
+
children: {
|
|
645
|
+
type: 'array',
|
|
646
|
+
description: 'Child layers to add inside the group',
|
|
647
|
+
items: {
|
|
648
|
+
type: 'object',
|
|
649
|
+
properties: {
|
|
650
|
+
type: { type: 'string', enum: ['text', 'image', 'video'], description: 'Child layer type' },
|
|
651
|
+
name: { type: 'string', description: 'Child layer name' },
|
|
652
|
+
content: { type: 'string', description: 'Text content' },
|
|
653
|
+
fontFamily: { type: 'string', description: 'Font family' },
|
|
654
|
+
fontSize: { type: 'number', description: 'Font size in pixels' },
|
|
655
|
+
fontWeight: { type: 'string', description: 'Font weight' },
|
|
656
|
+
color: { type: 'string', description: 'Text color in hex' },
|
|
657
|
+
textAlign: { type: 'string', enum: ['left', 'center', 'right'], description: 'Text alignment' },
|
|
658
|
+
letterSpacing: { type: 'number', description: 'Letter spacing' },
|
|
659
|
+
lineHeight: { type: 'number', description: 'Line height multiplier' },
|
|
660
|
+
textType: { type: 'string', enum: ['point', 'frame'], description: 'Text authoring mode' },
|
|
661
|
+
textTransformMode: { type: 'string', enum: ['scale', 'box'], description: 'Text sizing mode' },
|
|
662
|
+
textBoxWidth: { type: 'number', description: 'Width for text wrapping' },
|
|
663
|
+
frameWidthPx: { type: 'number', description: 'Frame width in CSS pixels' },
|
|
664
|
+
frameHeightPx: { type: 'number', description: 'Frame height in CSS pixels' },
|
|
665
|
+
autoSize: { type: 'string', enum: ['none', 'height', 'widthAndHeight'], description: 'Auto-size mode' },
|
|
666
|
+
mediaUrl: { type: 'string', description: 'Media URL (for image/video)' },
|
|
667
|
+
objectFit: { type: 'string', enum: ['cover', 'contain'], description: 'Object fit mode' },
|
|
668
|
+
width: { type: 'number', description: 'Child width (0-1 relative to group)' },
|
|
669
|
+
height: { type: 'number', description: 'Child height (0-1 relative to group)' },
|
|
670
|
+
opacity: { type: 'number', description: 'Opacity 0-1' },
|
|
671
|
+
},
|
|
672
|
+
required: ['type'],
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
},
|
|
676
|
+
required: ['children'],
|
|
677
|
+
},
|
|
678
|
+
},
|
|
456
679
|
{
|
|
457
680
|
name: 'modify_layer',
|
|
458
|
-
description: 'Modify an existing layer by ID',
|
|
681
|
+
description: 'Modify an existing layer by ID. Supports text, image, video, and group properties.',
|
|
459
682
|
inputSchema: {
|
|
460
683
|
type: 'object',
|
|
461
684
|
properties: {
|
|
@@ -472,6 +695,7 @@ exports.stateTools = [
|
|
|
472
695
|
name: { type: 'string', description: 'Layer name' },
|
|
473
696
|
// Text layer properties
|
|
474
697
|
content: { type: 'string' },
|
|
698
|
+
textType: { type: 'string', enum: ['point', 'frame'], description: 'Text authoring mode' },
|
|
475
699
|
fontFamily: { type: 'string' },
|
|
476
700
|
fontSize: { type: 'number' },
|
|
477
701
|
fontWeight: { type: 'string' },
|
|
@@ -479,6 +703,11 @@ exports.stateTools = [
|
|
|
479
703
|
textAlign: { type: 'string' },
|
|
480
704
|
letterSpacing: { type: 'number' },
|
|
481
705
|
lineHeight: { type: 'number' },
|
|
706
|
+
textTransformMode: { type: 'string', enum: ['scale', 'box'] },
|
|
707
|
+
textBoxWidth: { type: 'number' },
|
|
708
|
+
frameWidthPx: { type: 'number' },
|
|
709
|
+
frameHeightPx: { type: 'number' },
|
|
710
|
+
autoSize: { type: 'string', enum: ['none', 'height', 'widthAndHeight'] },
|
|
482
711
|
// Text animation properties (all type-specific params supported - see add_layer for descriptions)
|
|
483
712
|
animationType: {
|
|
484
713
|
type: 'string',
|
|
@@ -535,6 +764,22 @@ exports.stateTools = [
|
|
|
535
764
|
// Media properties
|
|
536
765
|
mediaUrl: { type: 'string' },
|
|
537
766
|
objectFit: { type: 'string' },
|
|
767
|
+
// Group layout properties
|
|
768
|
+
layoutMode: { type: 'string', enum: ['row', 'column'], description: 'Group layout direction' },
|
|
769
|
+
widthMode: { type: 'string', enum: ['hug', 'fixed'], description: 'Group horizontal sizing mode' },
|
|
770
|
+
heightMode: { type: 'string', enum: ['hug', 'fixed'], description: 'Group vertical sizing mode' },
|
|
771
|
+
gap: { type: 'number', description: 'Gap between children (normalized 0-1)' },
|
|
772
|
+
justifyContent: { type: 'string', enum: ['start', 'center', 'end', 'space-between', 'space-around', 'space-evenly'], description: 'Group main-axis alignment' },
|
|
773
|
+
alignItems: { type: 'string', enum: ['start', 'center', 'end'], description: 'Group cross-axis alignment' },
|
|
774
|
+
wrap: { type: 'boolean', description: 'Group wrap children' },
|
|
775
|
+
padding: { type: 'number', description: 'Group uniform padding' },
|
|
776
|
+
paddingTop: { type: 'number', description: 'Group top padding' },
|
|
777
|
+
paddingRight: { type: 'number', description: 'Group right padding' },
|
|
778
|
+
paddingBottom: { type: 'number', description: 'Group bottom padding' },
|
|
779
|
+
paddingLeft: { type: 'number', description: 'Group left padding' },
|
|
780
|
+
fillColor: { type: 'string', description: 'Group background color' },
|
|
781
|
+
fillOpacity: { type: 'number', description: 'Group background opacity' },
|
|
782
|
+
cornerRadius: { type: 'number', description: 'Group corner radius' },
|
|
538
783
|
},
|
|
539
784
|
required: ['layerId'],
|
|
540
785
|
},
|
|
@@ -765,13 +1010,79 @@ async function handleStateTool(name, args) {
|
|
|
765
1010
|
content: [{ type: 'text', text: 'Error: Background layer not found at index 0' }],
|
|
766
1011
|
};
|
|
767
1012
|
}
|
|
768
|
-
const
|
|
1013
|
+
const requestedType = params.type || 'solid';
|
|
1014
|
+
const contentType = requestedType === 'gradient' ? 'solid' : requestedType;
|
|
769
1015
|
bgLayer.contentType = contentType;
|
|
770
|
-
if (
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1016
|
+
if (requestedType === 'gradient') {
|
|
1017
|
+
const gradientStyle = params.gradientStyle === 'radial' ? 'radial' : 'linear';
|
|
1018
|
+
const explicitStops = parseGradientStopsInput(params.gradientStops, gradientStyle === 'radial' ? DEFAULT_RADIAL_GRADIENT_FILL.stops : DEFAULT_LINEAR_GRADIENT_FILL.stops);
|
|
1019
|
+
const gradient = gradientStyle === 'radial'
|
|
1020
|
+
? normalizeRadialGradientFill({
|
|
1021
|
+
type: 'radial',
|
|
1022
|
+
center: {
|
|
1023
|
+
x: Number.isFinite(params.gradientCenterX)
|
|
1024
|
+
? params.gradientCenterX
|
|
1025
|
+
: DEFAULT_RADIAL_GRADIENT_FILL.center.x,
|
|
1026
|
+
y: Number.isFinite(params.gradientCenterY)
|
|
1027
|
+
? params.gradientCenterY
|
|
1028
|
+
: DEFAULT_RADIAL_GRADIENT_FILL.center.y,
|
|
1029
|
+
},
|
|
1030
|
+
radius: Number.isFinite(params.gradientRadius)
|
|
1031
|
+
? params.gradientRadius
|
|
1032
|
+
: DEFAULT_RADIAL_GRADIENT_FILL.radius,
|
|
1033
|
+
stops: explicitStops ?? [
|
|
1034
|
+
{
|
|
1035
|
+
color: params.gradientStartColor || DEFAULT_RADIAL_GRADIENT_FILL.stops[0].color,
|
|
1036
|
+
opacity: DEFAULT_RADIAL_GRADIENT_FILL.stops[0].opacity,
|
|
1037
|
+
position: DEFAULT_RADIAL_GRADIENT_FILL.stops[0].position,
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
color: params.gradientEndColor || DEFAULT_RADIAL_GRADIENT_FILL.stops[1].color,
|
|
1041
|
+
opacity: DEFAULT_RADIAL_GRADIENT_FILL.stops[1].opacity,
|
|
1042
|
+
position: DEFAULT_RADIAL_GRADIENT_FILL.stops[1].position,
|
|
1043
|
+
},
|
|
1044
|
+
],
|
|
1045
|
+
})
|
|
1046
|
+
: normalizeLinearGradientFill({
|
|
1047
|
+
type: 'linear',
|
|
1048
|
+
angle: Number.isFinite(params.gradientAngle)
|
|
1049
|
+
? params.gradientAngle
|
|
1050
|
+
: DEFAULT_LINEAR_GRADIENT_FILL.angle,
|
|
1051
|
+
stops: explicitStops ?? [
|
|
1052
|
+
{
|
|
1053
|
+
color: params.gradientStartColor || DEFAULT_LINEAR_GRADIENT_FILL.stops[0].color,
|
|
1054
|
+
opacity: DEFAULT_LINEAR_GRADIENT_FILL.stops[0].opacity,
|
|
1055
|
+
position: DEFAULT_LINEAR_GRADIENT_FILL.stops[0].position,
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
color: params.gradientEndColor || DEFAULT_LINEAR_GRADIENT_FILL.stops[1].color,
|
|
1059
|
+
opacity: DEFAULT_LINEAR_GRADIENT_FILL.stops[1].opacity,
|
|
1060
|
+
position: DEFAULT_LINEAR_GRADIENT_FILL.stops[1].position,
|
|
1061
|
+
},
|
|
1062
|
+
],
|
|
1063
|
+
});
|
|
1064
|
+
bgLayer.fill = gradient;
|
|
1065
|
+
bgLayer.solidColor = gradient.stops[0].color;
|
|
1066
|
+
currentPoster.canvas.backgroundColor = gradient.stops[0].color;
|
|
1067
|
+
delete bgLayer.inputMedia;
|
|
1068
|
+
}
|
|
1069
|
+
else if (contentType === 'solid') {
|
|
1070
|
+
const solidColor = params.color ||
|
|
1071
|
+
(bgLayer.fill?.type === 'solid'
|
|
1072
|
+
? bgLayer.fill.color
|
|
1073
|
+
: bgLayer.fill
|
|
1074
|
+
? bgLayer.fill.stops[0]?.color
|
|
1075
|
+
: undefined) ||
|
|
1076
|
+
bgLayer.solidColor ||
|
|
1077
|
+
currentPoster.canvas.backgroundColor ||
|
|
1078
|
+
'#5c5c5c';
|
|
1079
|
+
bgLayer.solidColor = solidColor;
|
|
1080
|
+
bgLayer.fill = {
|
|
1081
|
+
type: 'solid',
|
|
1082
|
+
color: solidColor,
|
|
1083
|
+
opacity: 1,
|
|
1084
|
+
};
|
|
1085
|
+
currentPoster.canvas.backgroundColor = solidColor;
|
|
775
1086
|
delete bgLayer.inputMedia;
|
|
776
1087
|
}
|
|
777
1088
|
else if (contentType === 'image' && params.imageUrl) {
|
|
@@ -843,11 +1154,17 @@ async function handleStateTool(name, args) {
|
|
|
843
1154
|
let layer;
|
|
844
1155
|
if (layerType === 'text') {
|
|
845
1156
|
const animation = buildAnimationSettings(params);
|
|
1157
|
+
// textType is canonical, textTransformMode is legacy. textType wins if both provided.
|
|
1158
|
+
const requestedTextType = params.textType;
|
|
1159
|
+
const requestedTransformMode = params.textTransformMode;
|
|
1160
|
+
const textType = requestedTextType ?? (requestedTransformMode === 'scale' ? 'point' : 'frame');
|
|
1161
|
+
const textTransformMode = textType === 'point' ? 'scale' : 'box';
|
|
846
1162
|
// Use same defaults as app's DEFAULT_TEXT_LAYER to avoid encoding unnecessary params in URLs
|
|
847
1163
|
layer = {
|
|
848
1164
|
...baseLayer,
|
|
849
1165
|
type: 'text',
|
|
850
1166
|
content: params.content || 'Text',
|
|
1167
|
+
textType,
|
|
851
1168
|
fontFamily: normalizeFontFamily(params.fontFamily),
|
|
852
1169
|
fontSize: params.fontSize || 72,
|
|
853
1170
|
fontWeight: normalizeFontWeight(params.fontWeight),
|
|
@@ -855,7 +1172,12 @@ async function handleStateTool(name, args) {
|
|
|
855
1172
|
textAlign: params.textAlign || 'center',
|
|
856
1173
|
letterSpacing: params.letterSpacing ?? 0,
|
|
857
1174
|
lineHeight: params.lineHeight ?? 1.2,
|
|
858
|
-
|
|
1175
|
+
textTransformMode,
|
|
1176
|
+
...(params.textBoxWidth ? { textBoxWidth: params.textBoxWidth } : {}),
|
|
1177
|
+
...(params.frameWidthPx ? { frameWidthPx: params.frameWidthPx } : {}),
|
|
1178
|
+
...(params.frameHeightPx ? { frameHeightPx: params.frameHeightPx } : {}),
|
|
1179
|
+
...(params.autoSize ? { autoSize: params.autoSize } : {}),
|
|
1180
|
+
...(animation ? { animation } : {}),
|
|
859
1181
|
};
|
|
860
1182
|
}
|
|
861
1183
|
else if (layerType === 'image') {
|
|
@@ -910,6 +1232,154 @@ async function handleStateTool(name, args) {
|
|
|
910
1232
|
],
|
|
911
1233
|
};
|
|
912
1234
|
}
|
|
1235
|
+
case 'add_group': {
|
|
1236
|
+
if (!currentPoster) {
|
|
1237
|
+
return {
|
|
1238
|
+
content: [{ type: 'text', text: 'Error: No poster created. Use create_poster first.' }],
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
const children = params.children;
|
|
1242
|
+
if (!children || !Array.isArray(children) || children.length === 0) {
|
|
1243
|
+
return {
|
|
1244
|
+
content: [{ type: 'text', text: 'Error: children array is required and must have at least 1 child' }],
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
const groupId = generateId();
|
|
1248
|
+
const childLayers = [];
|
|
1249
|
+
for (const childParams of children) {
|
|
1250
|
+
const childType = childParams.type || 'text';
|
|
1251
|
+
const childId = generateId();
|
|
1252
|
+
const childBase = {
|
|
1253
|
+
id: childId,
|
|
1254
|
+
type: childType,
|
|
1255
|
+
name: childParams.name || `${childType.charAt(0).toUpperCase() + childType.slice(1)} Layer`,
|
|
1256
|
+
visible: true,
|
|
1257
|
+
locked: false,
|
|
1258
|
+
transform: {
|
|
1259
|
+
x: 0,
|
|
1260
|
+
y: 0,
|
|
1261
|
+
width: childParams.width ?? 1,
|
|
1262
|
+
height: childParams.height ?? 1,
|
|
1263
|
+
rotation: 0,
|
|
1264
|
+
opacity: childParams.opacity ?? 1,
|
|
1265
|
+
},
|
|
1266
|
+
};
|
|
1267
|
+
if (childType === 'text') {
|
|
1268
|
+
const requestedTextType = childParams.textType;
|
|
1269
|
+
const requestedTransformMode = childParams.textTransformMode;
|
|
1270
|
+
const textType = requestedTextType ?? (requestedTransformMode === 'scale' ? 'point' : 'frame');
|
|
1271
|
+
const textTransformMode = textType === 'point' ? 'scale' : 'box';
|
|
1272
|
+
childLayers.push({
|
|
1273
|
+
...childBase,
|
|
1274
|
+
type: 'text',
|
|
1275
|
+
content: childParams.content || 'Text',
|
|
1276
|
+
textType,
|
|
1277
|
+
fontFamily: normalizeFontFamily(childParams.fontFamily),
|
|
1278
|
+
fontSize: childParams.fontSize || 72,
|
|
1279
|
+
fontWeight: normalizeFontWeight(childParams.fontWeight),
|
|
1280
|
+
color: childParams.color || '#ffffff',
|
|
1281
|
+
textAlign: childParams.textAlign || 'center',
|
|
1282
|
+
letterSpacing: childParams.letterSpacing ?? 0,
|
|
1283
|
+
lineHeight: childParams.lineHeight ?? 1.2,
|
|
1284
|
+
textTransformMode,
|
|
1285
|
+
...(childParams.textBoxWidth ? { textBoxWidth: childParams.textBoxWidth } : {}),
|
|
1286
|
+
...(childParams.frameWidthPx ? { frameWidthPx: childParams.frameWidthPx } : {}),
|
|
1287
|
+
...(childParams.frameHeightPx ? { frameHeightPx: childParams.frameHeightPx } : {}),
|
|
1288
|
+
...(childParams.autoSize ? { autoSize: childParams.autoSize } : {}),
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
else if (childType === 'image') {
|
|
1292
|
+
if (!childParams.mediaUrl)
|
|
1293
|
+
continue;
|
|
1294
|
+
childLayers.push({
|
|
1295
|
+
...childBase,
|
|
1296
|
+
type: 'image',
|
|
1297
|
+
mediaUrl: childParams.mediaUrl,
|
|
1298
|
+
objectFit: childParams.objectFit || 'cover',
|
|
1299
|
+
brightness: 1,
|
|
1300
|
+
contrast: 1,
|
|
1301
|
+
saturation: 1,
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
else if (childType === 'video') {
|
|
1305
|
+
if (!childParams.mediaUrl)
|
|
1306
|
+
continue;
|
|
1307
|
+
childLayers.push({
|
|
1308
|
+
...childBase,
|
|
1309
|
+
type: 'video',
|
|
1310
|
+
mediaUrl: childParams.mediaUrl,
|
|
1311
|
+
objectFit: childParams.objectFit || 'cover',
|
|
1312
|
+
brightness: 1,
|
|
1313
|
+
contrast: 1,
|
|
1314
|
+
saturation: 1,
|
|
1315
|
+
loop: true,
|
|
1316
|
+
playbackSpeed: 1,
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
if (childLayers.length === 0) {
|
|
1321
|
+
return {
|
|
1322
|
+
content: [{ type: 'text', text: 'Error: No valid child layers could be created' }],
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
// Build padding
|
|
1326
|
+
const uniformPadding = params.padding ?? 0;
|
|
1327
|
+
const groupPadding = {
|
|
1328
|
+
top: params.paddingTop ?? uniformPadding,
|
|
1329
|
+
right: params.paddingRight ?? uniformPadding,
|
|
1330
|
+
bottom: params.paddingBottom ?? uniformPadding,
|
|
1331
|
+
left: params.paddingLeft ?? uniformPadding,
|
|
1332
|
+
};
|
|
1333
|
+
const layoutMode = params.layoutMode || 'column';
|
|
1334
|
+
const widthMode = params.widthMode ?? (params.width !== undefined ? 'fixed' : 'hug');
|
|
1335
|
+
const heightMode = params.heightMode ?? (params.height !== undefined ? 'fixed' : 'hug');
|
|
1336
|
+
const baseWidth = params.width ?? 0.6;
|
|
1337
|
+
const baseHeight = params.height ?? 0.4;
|
|
1338
|
+
const groupLayer = {
|
|
1339
|
+
id: groupId,
|
|
1340
|
+
type: 'group',
|
|
1341
|
+
name: params.name || 'Group',
|
|
1342
|
+
visible: true,
|
|
1343
|
+
locked: false,
|
|
1344
|
+
transform: {
|
|
1345
|
+
x: params.x ?? 0,
|
|
1346
|
+
y: params.y ?? 0,
|
|
1347
|
+
width: 1,
|
|
1348
|
+
height: 1,
|
|
1349
|
+
rotation: params.rotation ?? 0,
|
|
1350
|
+
opacity: params.opacity ?? 1,
|
|
1351
|
+
},
|
|
1352
|
+
children: childLayers,
|
|
1353
|
+
expanded: true,
|
|
1354
|
+
baseWidth,
|
|
1355
|
+
baseHeight,
|
|
1356
|
+
layoutMode,
|
|
1357
|
+
widthMode,
|
|
1358
|
+
heightMode,
|
|
1359
|
+
gap: params.gap ?? 0.02,
|
|
1360
|
+
justifyContent: params.justifyContent ?? 'start',
|
|
1361
|
+
alignItems: params.alignItems ?? 'center',
|
|
1362
|
+
wrap: params.wrap ?? false,
|
|
1363
|
+
padding: groupPadding,
|
|
1364
|
+
...(params.fillColor ? { fillColor: params.fillColor } : {}),
|
|
1365
|
+
...(params.fillOpacity !== undefined ? { fillOpacity: params.fillOpacity } : {}),
|
|
1366
|
+
...(params.cornerRadius !== undefined ? { cornerRadius: params.cornerRadius } : {}),
|
|
1367
|
+
};
|
|
1368
|
+
currentPoster.layers.push(groupLayer);
|
|
1369
|
+
return {
|
|
1370
|
+
content: [
|
|
1371
|
+
{
|
|
1372
|
+
type: 'text',
|
|
1373
|
+
text: JSON.stringify({
|
|
1374
|
+
success: true,
|
|
1375
|
+
message: `Group added with ${childLayers.length} children (${groupLayer.layoutMode} layout)`,
|
|
1376
|
+
group: groupLayer,
|
|
1377
|
+
totalLayers: currentPoster.layers.length,
|
|
1378
|
+
}, null, 2),
|
|
1379
|
+
},
|
|
1380
|
+
],
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
913
1383
|
case 'modify_layer': {
|
|
914
1384
|
if (!currentPoster) {
|
|
915
1385
|
return {
|
|
@@ -962,6 +1432,22 @@ async function handleStateTool(name, args) {
|
|
|
962
1432
|
textLayer.letterSpacing = params.letterSpacing;
|
|
963
1433
|
if (params.lineHeight !== undefined)
|
|
964
1434
|
textLayer.lineHeight = params.lineHeight;
|
|
1435
|
+
// Reconcile textType (canonical) vs textTransformMode (legacy). textType wins.
|
|
1436
|
+
if (params.textType !== undefined || params.textTransformMode !== undefined) {
|
|
1437
|
+
const resolvedTextType = params.textType !== undefined
|
|
1438
|
+
? params.textType
|
|
1439
|
+
: (params.textTransformMode === 'scale' ? 'point' : 'frame');
|
|
1440
|
+
textLayer.textType = resolvedTextType;
|
|
1441
|
+
textLayer.textTransformMode = resolvedTextType === 'point' ? 'scale' : 'box';
|
|
1442
|
+
}
|
|
1443
|
+
if (params.textBoxWidth !== undefined)
|
|
1444
|
+
textLayer.textBoxWidth = params.textBoxWidth;
|
|
1445
|
+
if (params.frameWidthPx !== undefined)
|
|
1446
|
+
textLayer.frameWidthPx = params.frameWidthPx;
|
|
1447
|
+
if (params.frameHeightPx !== undefined)
|
|
1448
|
+
textLayer.frameHeightPx = params.frameHeightPx;
|
|
1449
|
+
if (params.autoSize !== undefined)
|
|
1450
|
+
textLayer.autoSize = params.autoSize;
|
|
965
1451
|
// Update animation settings
|
|
966
1452
|
if (params.animationType !== undefined) {
|
|
967
1453
|
if (params.animationType === 'none') {
|
|
@@ -1095,6 +1581,54 @@ async function handleStateTool(name, args) {
|
|
|
1095
1581
|
if (params.objectFit !== undefined)
|
|
1096
1582
|
mediaLayer.objectFit = params.objectFit;
|
|
1097
1583
|
}
|
|
1584
|
+
// Update group layout properties
|
|
1585
|
+
if (layer.type === 'group') {
|
|
1586
|
+
const groupLayer = layer;
|
|
1587
|
+
if (params.layoutMode !== undefined)
|
|
1588
|
+
groupLayer.layoutMode = params.layoutMode;
|
|
1589
|
+
if (params.widthMode !== undefined)
|
|
1590
|
+
groupLayer.widthMode = params.widthMode;
|
|
1591
|
+
if (params.heightMode !== undefined)
|
|
1592
|
+
groupLayer.heightMode = params.heightMode;
|
|
1593
|
+
if (params.gap !== undefined)
|
|
1594
|
+
groupLayer.gap = params.gap;
|
|
1595
|
+
if (params.justifyContent !== undefined)
|
|
1596
|
+
groupLayer.justifyContent = params.justifyContent;
|
|
1597
|
+
if (params.alignItems !== undefined)
|
|
1598
|
+
groupLayer.alignItems = params.alignItems;
|
|
1599
|
+
if (params.wrap !== undefined)
|
|
1600
|
+
groupLayer.wrap = params.wrap;
|
|
1601
|
+
if (params.fillColor !== undefined)
|
|
1602
|
+
groupLayer.fillColor = params.fillColor;
|
|
1603
|
+
if (params.fillOpacity !== undefined)
|
|
1604
|
+
groupLayer.fillOpacity = params.fillOpacity;
|
|
1605
|
+
if (params.cornerRadius !== undefined)
|
|
1606
|
+
groupLayer.cornerRadius = params.cornerRadius;
|
|
1607
|
+
// Padding: uniform or individual overrides
|
|
1608
|
+
if (params.padding !== undefined || params.paddingTop !== undefined || params.paddingRight !== undefined || params.paddingBottom !== undefined || params.paddingLeft !== undefined) {
|
|
1609
|
+
const existing = groupLayer.padding || { top: 0, right: 0, bottom: 0, left: 0 };
|
|
1610
|
+
const uniform = params.padding ?? undefined;
|
|
1611
|
+
groupLayer.padding = {
|
|
1612
|
+
top: params.paddingTop ?? uniform ?? existing.top,
|
|
1613
|
+
right: params.paddingRight ?? uniform ?? existing.right,
|
|
1614
|
+
bottom: params.paddingBottom ?? uniform ?? existing.bottom,
|
|
1615
|
+
left: params.paddingLeft ?? uniform ?? existing.left,
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
// Allow updating baseWidth/baseHeight via width/height params
|
|
1619
|
+
if (params.width !== undefined) {
|
|
1620
|
+
groupLayer.baseWidth = params.width;
|
|
1621
|
+
if (params.widthMode === undefined) {
|
|
1622
|
+
groupLayer.widthMode = 'fixed';
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
if (params.height !== undefined) {
|
|
1626
|
+
groupLayer.baseHeight = params.height;
|
|
1627
|
+
if (params.heightMode === undefined) {
|
|
1628
|
+
groupLayer.heightMode = 'fixed';
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1098
1632
|
return {
|
|
1099
1633
|
content: [
|
|
1100
1634
|
{
|
|
@@ -1250,12 +1784,12 @@ async function handleStateTool(name, args) {
|
|
|
1250
1784
|
}
|
|
1251
1785
|
else if (settingsKey === 'chromatic') {
|
|
1252
1786
|
effect.chromatic = {
|
|
1253
|
-
shape: '
|
|
1787
|
+
shape: 'square',
|
|
1254
1788
|
radius: 4,
|
|
1255
1789
|
rotateR: 15,
|
|
1256
1790
|
rotateG: 75,
|
|
1257
1791
|
rotateB: 0,
|
|
1258
|
-
scatter:
|
|
1792
|
+
scatter: 1,
|
|
1259
1793
|
blending: 1,
|
|
1260
1794
|
blendingMode: 'linear',
|
|
1261
1795
|
greyscale: false,
|
|
@@ -1273,12 +1807,12 @@ async function handleStateTool(name, args) {
|
|
|
1273
1807
|
// Digital glitch defaults
|
|
1274
1808
|
blockSize: 0.5,
|
|
1275
1809
|
displacement: 0.5,
|
|
1276
|
-
blockOpacity:
|
|
1810
|
+
blockOpacity: 0,
|
|
1277
1811
|
colorSplit: 0.5,
|
|
1278
1812
|
lineTear: 0.5,
|
|
1279
1813
|
pixelate: 0.3,
|
|
1280
1814
|
// Weird glitch defaults
|
|
1281
|
-
glitchChance: 0.
|
|
1815
|
+
glitchChance: 0.66,
|
|
1282
1816
|
glitchSpeed: 7.0,
|
|
1283
1817
|
sliceDensity: 14.0,
|
|
1284
1818
|
sliceStrength: 0.38,
|