@efectoapp/mcp-server 0.1.23 → 0.1.26
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 +7 -6
- package/dist/tools/output.d.ts.map +1 -1
- package/dist/tools/output.js +45 -8
- package/dist/tools/output.js.map +1 -1
- package/dist/tools/render.d.ts.map +1 -1
- package/dist/tools/render.js +7 -12
- package/dist/tools/render.js.map +1 -1
- package/dist/tools/state.d.ts +0 -5
- package/dist/tools/state.d.ts.map +1 -1
- package/dist/tools/state.js +168 -261
- package/dist/tools/state.js.map +1 -1
- package/package.json +1 -1
package/dist/tools/state.js
CHANGED
|
@@ -14,13 +14,9 @@
|
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
16
|
exports.stateTools = void 0;
|
|
17
|
-
exports.ensurePoster = ensurePoster;
|
|
18
17
|
exports.resetState = resetState;
|
|
19
18
|
exports.getCurrentState = getCurrentState;
|
|
20
19
|
exports.handleStateTool = handleStateTool;
|
|
21
|
-
const os_1 = require("os");
|
|
22
|
-
const fs_1 = require("fs");
|
|
23
|
-
const path_1 = require("path");
|
|
24
20
|
// Helper to clamp values to valid ranges
|
|
25
21
|
function clamp(value, min, max, fallback) {
|
|
26
22
|
const v = value ?? fallback;
|
|
@@ -124,6 +120,24 @@ function parseNormalizedFrameDimension(input) {
|
|
|
124
120
|
return undefined;
|
|
125
121
|
return clamp(value, 0.01, 2, value);
|
|
126
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Auto-fit font size so the longest word fits on one line within frameWidthPx.
|
|
125
|
+
* Uses a conservative 0.72 average-char-width factor (bold uppercase in DM Sans
|
|
126
|
+
* averages ~0.68–0.72 of fontSize). The +0.5 accounts for text padding
|
|
127
|
+
* (TEXT_PADDING_FACTOR * 2 = 0.25 * 2).
|
|
128
|
+
*/
|
|
129
|
+
function autoFitFontSize(content, fontSize, letterSpacing, frameWidthPx) {
|
|
130
|
+
const words = content.split(/\s+/);
|
|
131
|
+
const longestWord = words.reduce((a, b) => Array.from(a).length >= Array.from(b).length ? a : b, '');
|
|
132
|
+
const charCount = Array.from(longestWord).length;
|
|
133
|
+
if (charCount === 0)
|
|
134
|
+
return fontSize;
|
|
135
|
+
const spacingPx = Math.max(0, charCount - 1) * Math.abs(letterSpacing);
|
|
136
|
+
const maxFontSize = (frameWidthPx - spacingPx) / (0.72 * charCount + 0.5);
|
|
137
|
+
if (fontSize <= maxFontSize)
|
|
138
|
+
return fontSize;
|
|
139
|
+
return Math.max(12, Math.floor(maxFontSize));
|
|
140
|
+
}
|
|
127
141
|
function textNormalizedToPx(normalized, artboardAspect, axis) {
|
|
128
142
|
if (axis === 'height') {
|
|
129
143
|
return normalized / TEXT_WORLD_SCALE_FACTOR;
|
|
@@ -399,75 +413,9 @@ function normalizeFontFamily(input) {
|
|
|
399
413
|
}
|
|
400
414
|
// Current poster state (single poster for now)
|
|
401
415
|
let currentPoster = null;
|
|
402
|
-
// --- File-based state persistence ---
|
|
403
|
-
// Some MCP clients spawn a fresh process per tool call, losing in-memory state.
|
|
404
|
-
// We persist to a temp file so state survives process restarts.
|
|
405
|
-
const STATE_FILE_PATH = (0, path_1.join)((0, os_1.tmpdir)(), 'efecto-mcp-state.json');
|
|
406
|
-
const STATE_MAX_AGE_MS = 60 * 60 * 1000; // 1 hour
|
|
407
|
-
/** Write current poster state to the temp file (best-effort). */
|
|
408
|
-
function persistState() {
|
|
409
|
-
if (!currentPoster)
|
|
410
|
-
return;
|
|
411
|
-
try {
|
|
412
|
-
const payload = JSON.stringify({ timestamp: Date.now(), state: currentPoster });
|
|
413
|
-
(0, fs_1.writeFileSync)(STATE_FILE_PATH, payload, 'utf-8');
|
|
414
|
-
}
|
|
415
|
-
catch {
|
|
416
|
-
// Silently ignore — falls back to in-memory only
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
/** Load persisted state from the temp file if valid and not stale. */
|
|
420
|
-
function loadPersistedState() {
|
|
421
|
-
try {
|
|
422
|
-
const raw = (0, fs_1.readFileSync)(STATE_FILE_PATH, 'utf-8');
|
|
423
|
-
const parsed = JSON.parse(raw);
|
|
424
|
-
if (!parsed.state || !parsed.timestamp)
|
|
425
|
-
return null;
|
|
426
|
-
if (Date.now() - parsed.timestamp > STATE_MAX_AGE_MS)
|
|
427
|
-
return null;
|
|
428
|
-
return parsed.state;
|
|
429
|
-
}
|
|
430
|
-
catch {
|
|
431
|
-
return null;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* Ensure a poster exists. Restores from persisted file if needed,
|
|
436
|
-
* or auto-creates a default poster as a last resort.
|
|
437
|
-
*/
|
|
438
|
-
function ensurePoster() {
|
|
439
|
-
if (currentPoster)
|
|
440
|
-
return currentPoster;
|
|
441
|
-
// Try restoring from persisted state
|
|
442
|
-
const persisted = loadPersistedState();
|
|
443
|
-
if (persisted) {
|
|
444
|
-
currentPoster = persisted;
|
|
445
|
-
return currentPoster;
|
|
446
|
-
}
|
|
447
|
-
// Auto-create a default poster (same defaults as create_poster)
|
|
448
|
-
currentPoster = {
|
|
449
|
-
canvas: {
|
|
450
|
-
aspectRatio: '9:16',
|
|
451
|
-
backgroundColor: '#1a1a1a',
|
|
452
|
-
layoutMode: 'column',
|
|
453
|
-
alignItems: 'center',
|
|
454
|
-
justifyContent: 'start',
|
|
455
|
-
gap: 0.04,
|
|
456
|
-
padding: { top: 0.08, right: 0.08, bottom: 0.08, left: 0.08 },
|
|
457
|
-
},
|
|
458
|
-
layers: [createDefaultBackgroundLayer('#1a1a1a')],
|
|
459
|
-
postProcesses: [],
|
|
460
|
-
};
|
|
461
|
-
persistState();
|
|
462
|
-
return currentPoster;
|
|
463
|
-
}
|
|
464
416
|
// Reset state (useful for testing)
|
|
465
417
|
function resetState() {
|
|
466
418
|
currentPoster = null;
|
|
467
|
-
try {
|
|
468
|
-
(0, fs_1.unlinkSync)(STATE_FILE_PATH);
|
|
469
|
-
}
|
|
470
|
-
catch { /* ignore */ }
|
|
471
419
|
}
|
|
472
420
|
// Get current state
|
|
473
421
|
function getCurrentState() {
|
|
@@ -800,113 +748,6 @@ function parseGradientStopsInput(input, fallbackStops) {
|
|
|
800
748
|
.filter((stop) => stop !== null);
|
|
801
749
|
return parsed.length >= MIN_GRADIENT_STOPS ? parsed : null;
|
|
802
750
|
}
|
|
803
|
-
/**
|
|
804
|
-
* Build a Fill object from flat MCP tool params (fillType, gradientStops, etc.).
|
|
805
|
-
* Returns null when no gradient params are present (caller should fall back to solid).
|
|
806
|
-
*/
|
|
807
|
-
function buildFillFromParams(params) {
|
|
808
|
-
const fillType = params.fillType;
|
|
809
|
-
const hasGradientParams = fillType === 'linear' || fillType === 'radial' ||
|
|
810
|
-
params.gradientStyle !== undefined ||
|
|
811
|
-
params.gradientStops !== undefined ||
|
|
812
|
-
params.gradientStartColor !== undefined ||
|
|
813
|
-
params.gradientEndColor !== undefined;
|
|
814
|
-
if (!hasGradientParams)
|
|
815
|
-
return null;
|
|
816
|
-
const gradientStyle = fillType === 'radial' || params.gradientStyle === 'radial' ? 'radial' : 'linear';
|
|
817
|
-
const defaults = gradientStyle === 'radial'
|
|
818
|
-
? DEFAULT_RADIAL_GRADIENT_FILL
|
|
819
|
-
: DEFAULT_LINEAR_GRADIENT_FILL;
|
|
820
|
-
const explicitStops = parseGradientStopsInput(params.gradientStops, defaults.stops);
|
|
821
|
-
const stops = explicitStops ?? [
|
|
822
|
-
{
|
|
823
|
-
color: params.gradientStartColor || defaults.stops[0].color,
|
|
824
|
-
opacity: defaults.stops[0].opacity,
|
|
825
|
-
position: defaults.stops[0].position,
|
|
826
|
-
},
|
|
827
|
-
{
|
|
828
|
-
color: params.gradientEndColor || defaults.stops[1].color,
|
|
829
|
-
opacity: defaults.stops[1].opacity,
|
|
830
|
-
position: defaults.stops[1].position,
|
|
831
|
-
},
|
|
832
|
-
];
|
|
833
|
-
if (gradientStyle === 'radial') {
|
|
834
|
-
return normalizeRadialGradientFill({
|
|
835
|
-
type: 'radial',
|
|
836
|
-
center: {
|
|
837
|
-
x: Number.isFinite(params.gradientCenterX)
|
|
838
|
-
? params.gradientCenterX
|
|
839
|
-
: DEFAULT_RADIAL_GRADIENT_FILL.center.x,
|
|
840
|
-
y: Number.isFinite(params.gradientCenterY)
|
|
841
|
-
? params.gradientCenterY
|
|
842
|
-
: DEFAULT_RADIAL_GRADIENT_FILL.center.y,
|
|
843
|
-
},
|
|
844
|
-
radius: Number.isFinite(params.gradientRadius)
|
|
845
|
-
? params.gradientRadius
|
|
846
|
-
: DEFAULT_RADIAL_GRADIENT_FILL.radius,
|
|
847
|
-
stops,
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
return normalizeLinearGradientFill({
|
|
851
|
-
type: 'linear',
|
|
852
|
-
angle: Number.isFinite(params.gradientAngle)
|
|
853
|
-
? params.gradientAngle
|
|
854
|
-
: DEFAULT_LINEAR_GRADIENT_FILL.angle,
|
|
855
|
-
stops,
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
/** Reusable schema properties for gradient fill params — spread into tool schemas. */
|
|
859
|
-
const GRADIENT_FILL_SCHEMA_PROPS = {
|
|
860
|
-
fillType: {
|
|
861
|
-
type: 'string',
|
|
862
|
-
enum: ['solid', 'linear', 'radial'],
|
|
863
|
-
description: 'Fill type: "solid" (default), "linear" gradient, or "radial" gradient',
|
|
864
|
-
},
|
|
865
|
-
gradientStops: {
|
|
866
|
-
type: 'array',
|
|
867
|
-
description: 'Gradient color stops (2-8). Overrides start/end colors when provided.',
|
|
868
|
-
items: {
|
|
869
|
-
type: 'object',
|
|
870
|
-
properties: {
|
|
871
|
-
color: { type: 'string', description: 'Stop color in hex' },
|
|
872
|
-
opacity: { type: 'number', description: 'Stop opacity (0-1)' },
|
|
873
|
-
position: { type: 'number', description: 'Stop position (0-1)' },
|
|
874
|
-
},
|
|
875
|
-
required: ['color', 'position'],
|
|
876
|
-
},
|
|
877
|
-
minItems: 2,
|
|
878
|
-
maxItems: 8,
|
|
879
|
-
},
|
|
880
|
-
gradientAngle: {
|
|
881
|
-
type: 'number',
|
|
882
|
-
description: 'Linear gradient angle in degrees (default 135)',
|
|
883
|
-
},
|
|
884
|
-
gradientStartColor: {
|
|
885
|
-
type: 'string',
|
|
886
|
-
description: 'Gradient start color in hex (shortcut for 2-stop gradient)',
|
|
887
|
-
},
|
|
888
|
-
gradientEndColor: {
|
|
889
|
-
type: 'string',
|
|
890
|
-
description: 'Gradient end color in hex (shortcut for 2-stop gradient)',
|
|
891
|
-
},
|
|
892
|
-
gradientStyle: {
|
|
893
|
-
type: 'string',
|
|
894
|
-
enum: ['linear', 'radial'],
|
|
895
|
-
description: 'Gradient style (alternative to fillType for setting gradient type)',
|
|
896
|
-
},
|
|
897
|
-
gradientCenterX: {
|
|
898
|
-
type: 'number',
|
|
899
|
-
description: 'Radial gradient center X (0-1)',
|
|
900
|
-
},
|
|
901
|
-
gradientCenterY: {
|
|
902
|
-
type: 'number',
|
|
903
|
-
description: 'Radial gradient center Y (0-1)',
|
|
904
|
-
},
|
|
905
|
-
gradientRadius: {
|
|
906
|
-
type: 'number',
|
|
907
|
-
description: 'Radial gradient radius (0.05-2)',
|
|
908
|
-
},
|
|
909
|
-
};
|
|
910
751
|
// Tool definitions
|
|
911
752
|
exports.stateTools = [
|
|
912
753
|
{
|
|
@@ -1115,7 +956,6 @@ exports.stateTools = [
|
|
|
1115
956
|
// Shape style properties (rectangle/ellipse/polygon/star/line)
|
|
1116
957
|
fillColor: { type: 'string', description: 'Shape fill color in hex (e.g. #ffffff)' },
|
|
1117
958
|
fillOpacity: { type: 'number', description: 'Shape fill opacity (0-1)' },
|
|
1118
|
-
...GRADIENT_FILL_SCHEMA_PROPS,
|
|
1119
959
|
strokeColor: { type: 'string', description: 'Shape stroke color in hex' },
|
|
1120
960
|
strokeWidth: { type: 'number', description: 'Shape stroke width (normalized; e.g. 0.005-0.02)' },
|
|
1121
961
|
strokeOpacity: { type: 'number', description: 'Shape stroke opacity (0-1)' },
|
|
@@ -1324,7 +1164,6 @@ exports.stateTools = [
|
|
|
1324
1164
|
// Shape style
|
|
1325
1165
|
fillColor: { type: 'string', description: 'Shape fill color (hex)' },
|
|
1326
1166
|
fillOpacity: { type: 'number', description: 'Shape fill opacity (0-1)' },
|
|
1327
|
-
...GRADIENT_FILL_SCHEMA_PROPS,
|
|
1328
1167
|
strokeColor: { type: 'string', description: 'Shape stroke color (hex)' },
|
|
1329
1168
|
strokeWidth: { type: 'number', description: 'Shape stroke width (normalized; e.g. 0.005-0.02)' },
|
|
1330
1169
|
strokeOpacity: { type: 'number', description: 'Shape stroke opacity (0-1)' },
|
|
@@ -1496,7 +1335,6 @@ exports.stateTools = [
|
|
|
1496
1335
|
paddingLeft: { type: 'number', description: 'Padding left as % of canvas' },
|
|
1497
1336
|
fillColor: { type: 'string', description: 'Fill color (group background OR shape fill)' },
|
|
1498
1337
|
fillOpacity: { type: 'number', description: 'Fill opacity (group background OR shape fill)' },
|
|
1499
|
-
...GRADIENT_FILL_SCHEMA_PROPS,
|
|
1500
1338
|
cornerRadius: { type: 'number', description: 'Corner radius (group OR shape)' },
|
|
1501
1339
|
// Shadow properties (all layer types)
|
|
1502
1340
|
shadowOffsetX: { type: 'number', description: 'Shadow horizontal offset in CSS px' },
|
|
@@ -1769,7 +1607,6 @@ async function handleStateTool(name, args) {
|
|
|
1769
1607
|
layers: [createDefaultBackgroundLayer(backgroundColor)],
|
|
1770
1608
|
postProcesses: [],
|
|
1771
1609
|
};
|
|
1772
|
-
persistState();
|
|
1773
1610
|
return {
|
|
1774
1611
|
content: [
|
|
1775
1612
|
{
|
|
@@ -1785,7 +1622,11 @@ async function handleStateTool(name, args) {
|
|
|
1785
1622
|
};
|
|
1786
1623
|
}
|
|
1787
1624
|
case 'clear_poster': {
|
|
1788
|
-
|
|
1625
|
+
if (!currentPoster) {
|
|
1626
|
+
return {
|
|
1627
|
+
content: [{ type: 'text', text: 'Error: No poster created. Use create_poster first.' }],
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1789
1630
|
const bgLayer = currentPoster.layers.find((l) => l.type === 'background');
|
|
1790
1631
|
const defaultBg = createDefaultBackgroundLayer(currentPoster.canvas.backgroundColor || '#1a1a1a');
|
|
1791
1632
|
currentPoster = {
|
|
@@ -1793,7 +1634,6 @@ async function handleStateTool(name, args) {
|
|
|
1793
1634
|
layers: bgLayer ? [bgLayer] : [defaultBg],
|
|
1794
1635
|
postProcesses: [],
|
|
1795
1636
|
};
|
|
1796
|
-
persistState();
|
|
1797
1637
|
return {
|
|
1798
1638
|
content: [{
|
|
1799
1639
|
type: 'text',
|
|
@@ -1805,7 +1645,11 @@ async function handleStateTool(name, args) {
|
|
|
1805
1645
|
};
|
|
1806
1646
|
}
|
|
1807
1647
|
case 'set_page_layout': {
|
|
1808
|
-
|
|
1648
|
+
if (!currentPoster) {
|
|
1649
|
+
return {
|
|
1650
|
+
content: [{ type: 'text', text: 'Error: No poster created. Use create_poster first.' }],
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1809
1653
|
const layoutMode = params.layoutMode;
|
|
1810
1654
|
if (layoutMode !== undefined) {
|
|
1811
1655
|
if (layoutMode !== 'absolute' && layoutMode !== 'row' && layoutMode !== 'column') {
|
|
@@ -1843,7 +1687,6 @@ async function handleStateTool(name, args) {
|
|
|
1843
1687
|
};
|
|
1844
1688
|
currentPoster.canvas.padding = nextPadding;
|
|
1845
1689
|
}
|
|
1846
|
-
persistState();
|
|
1847
1690
|
return {
|
|
1848
1691
|
content: [
|
|
1849
1692
|
{
|
|
@@ -1858,7 +1701,11 @@ async function handleStateTool(name, args) {
|
|
|
1858
1701
|
};
|
|
1859
1702
|
}
|
|
1860
1703
|
case 'modify_canvas': {
|
|
1861
|
-
|
|
1704
|
+
if (!currentPoster) {
|
|
1705
|
+
return {
|
|
1706
|
+
content: [{ type: 'text', text: 'Error: No poster created. Use create_poster first.' }],
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1862
1709
|
const canvas = currentPoster.canvas;
|
|
1863
1710
|
if (params.aspectRatio)
|
|
1864
1711
|
canvas.aspectRatio = params.aspectRatio;
|
|
@@ -1868,7 +1715,6 @@ async function handleStateTool(name, args) {
|
|
|
1868
1715
|
canvas.canvasColor = params.canvasColor;
|
|
1869
1716
|
if (params.overflow !== undefined)
|
|
1870
1717
|
canvas.overflow = params.overflow;
|
|
1871
|
-
persistState();
|
|
1872
1718
|
return {
|
|
1873
1719
|
content: [
|
|
1874
1720
|
{
|
|
@@ -1887,7 +1733,11 @@ async function handleStateTool(name, args) {
|
|
|
1887
1733
|
};
|
|
1888
1734
|
}
|
|
1889
1735
|
case 'set_background': {
|
|
1890
|
-
|
|
1736
|
+
if (!currentPoster) {
|
|
1737
|
+
return {
|
|
1738
|
+
content: [{ type: 'text', text: 'Error: No poster created. Use create_poster first.' }],
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1891
1741
|
// Background is always at index 0
|
|
1892
1742
|
const bgLayer = currentPoster.layers[0];
|
|
1893
1743
|
if (bgLayer.type !== 'background') {
|
|
@@ -1899,8 +1749,53 @@ async function handleStateTool(name, args) {
|
|
|
1899
1749
|
const contentType = requestedType === 'gradient' ? 'solid' : requestedType;
|
|
1900
1750
|
bgLayer.contentType = contentType;
|
|
1901
1751
|
if (requestedType === 'gradient') {
|
|
1902
|
-
const
|
|
1903
|
-
|
|
1752
|
+
const gradientStyle = params.gradientStyle === 'radial' ? 'radial' : 'linear';
|
|
1753
|
+
const explicitStops = parseGradientStopsInput(params.gradientStops, gradientStyle === 'radial' ? DEFAULT_RADIAL_GRADIENT_FILL.stops : DEFAULT_LINEAR_GRADIENT_FILL.stops);
|
|
1754
|
+
const gradient = gradientStyle === 'radial'
|
|
1755
|
+
? normalizeRadialGradientFill({
|
|
1756
|
+
type: 'radial',
|
|
1757
|
+
center: {
|
|
1758
|
+
x: Number.isFinite(params.gradientCenterX)
|
|
1759
|
+
? params.gradientCenterX
|
|
1760
|
+
: DEFAULT_RADIAL_GRADIENT_FILL.center.x,
|
|
1761
|
+
y: Number.isFinite(params.gradientCenterY)
|
|
1762
|
+
? params.gradientCenterY
|
|
1763
|
+
: DEFAULT_RADIAL_GRADIENT_FILL.center.y,
|
|
1764
|
+
},
|
|
1765
|
+
radius: Number.isFinite(params.gradientRadius)
|
|
1766
|
+
? params.gradientRadius
|
|
1767
|
+
: DEFAULT_RADIAL_GRADIENT_FILL.radius,
|
|
1768
|
+
stops: explicitStops ?? [
|
|
1769
|
+
{
|
|
1770
|
+
color: params.gradientStartColor || DEFAULT_RADIAL_GRADIENT_FILL.stops[0].color,
|
|
1771
|
+
opacity: DEFAULT_RADIAL_GRADIENT_FILL.stops[0].opacity,
|
|
1772
|
+
position: DEFAULT_RADIAL_GRADIENT_FILL.stops[0].position,
|
|
1773
|
+
},
|
|
1774
|
+
{
|
|
1775
|
+
color: params.gradientEndColor || DEFAULT_RADIAL_GRADIENT_FILL.stops[1].color,
|
|
1776
|
+
opacity: DEFAULT_RADIAL_GRADIENT_FILL.stops[1].opacity,
|
|
1777
|
+
position: DEFAULT_RADIAL_GRADIENT_FILL.stops[1].position,
|
|
1778
|
+
},
|
|
1779
|
+
],
|
|
1780
|
+
})
|
|
1781
|
+
: normalizeLinearGradientFill({
|
|
1782
|
+
type: 'linear',
|
|
1783
|
+
angle: Number.isFinite(params.gradientAngle)
|
|
1784
|
+
? params.gradientAngle
|
|
1785
|
+
: DEFAULT_LINEAR_GRADIENT_FILL.angle,
|
|
1786
|
+
stops: explicitStops ?? [
|
|
1787
|
+
{
|
|
1788
|
+
color: params.gradientStartColor || DEFAULT_LINEAR_GRADIENT_FILL.stops[0].color,
|
|
1789
|
+
opacity: DEFAULT_LINEAR_GRADIENT_FILL.stops[0].opacity,
|
|
1790
|
+
position: DEFAULT_LINEAR_GRADIENT_FILL.stops[0].position,
|
|
1791
|
+
},
|
|
1792
|
+
{
|
|
1793
|
+
color: params.gradientEndColor || DEFAULT_LINEAR_GRADIENT_FILL.stops[1].color,
|
|
1794
|
+
opacity: DEFAULT_LINEAR_GRADIENT_FILL.stops[1].opacity,
|
|
1795
|
+
position: DEFAULT_LINEAR_GRADIENT_FILL.stops[1].position,
|
|
1796
|
+
},
|
|
1797
|
+
],
|
|
1798
|
+
});
|
|
1904
1799
|
bgLayer.fill = gradient;
|
|
1905
1800
|
bgLayer.solidColor = gradient.stops[0].color;
|
|
1906
1801
|
currentPoster.canvas.backgroundColor = gradient.stops[0].color;
|
|
@@ -1945,7 +1840,6 @@ async function handleStateTool(name, args) {
|
|
|
1945
1840
|
objectFit: params.objectFit || 'cover',
|
|
1946
1841
|
};
|
|
1947
1842
|
}
|
|
1948
|
-
persistState();
|
|
1949
1843
|
return {
|
|
1950
1844
|
content: [
|
|
1951
1845
|
{
|
|
@@ -1960,7 +1854,11 @@ async function handleStateTool(name, args) {
|
|
|
1960
1854
|
};
|
|
1961
1855
|
}
|
|
1962
1856
|
case 'add_layer': {
|
|
1963
|
-
|
|
1857
|
+
if (!currentPoster) {
|
|
1858
|
+
return {
|
|
1859
|
+
content: [{ type: 'text', text: 'Error: No poster created. Use create_poster first.' }],
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1964
1862
|
const layerType = params.type;
|
|
1965
1863
|
if (!layerType) {
|
|
1966
1864
|
return {
|
|
@@ -2018,16 +1916,25 @@ async function handleStateTool(name, args) {
|
|
|
2018
1916
|
: undefined;
|
|
2019
1917
|
const autoSize = requestedAutoSize ?? 'height';
|
|
2020
1918
|
if (frameWidthPx == null && textBoxWidth == null) {
|
|
2021
|
-
|
|
1919
|
+
// Account for canvas padding so the frame width matches what the
|
|
1920
|
+
// layout engine will actually give us after shrink-to-fit.
|
|
1921
|
+
const canvasPad = currentPoster.canvas.padding || { top: 0, right: 0, bottom: 0, left: 0 };
|
|
1922
|
+
const canvasContentW = 1.0 - (canvasPad.left || 0) - (canvasPad.right || 0);
|
|
1923
|
+
const defaultFrameWidthNormalized = Math.min(fontSize >= 72 ? 0.88 : 0.78, canvasContentW * 0.95);
|
|
2022
1924
|
frameWidthPx = textNormalizedToPx(defaultFrameWidthNormalized, artboardAspect, 'width');
|
|
2023
1925
|
}
|
|
1926
|
+
// Auto-fit: shrink font if the longest word can't fit on one line
|
|
1927
|
+
const letterSpacing = params.letterSpacing ?? 0;
|
|
1928
|
+
const fittedFontSize = frameWidthPx != null
|
|
1929
|
+
? autoFitFontSize(content, fontSize, letterSpacing, frameWidthPx)
|
|
1930
|
+
: fontSize;
|
|
2024
1931
|
layer = {
|
|
2025
1932
|
...baseLayer,
|
|
2026
1933
|
type: 'text',
|
|
2027
1934
|
content,
|
|
2028
1935
|
textType: 'frame',
|
|
2029
1936
|
fontFamily: normalizeFontFamily(params.fontFamily),
|
|
2030
|
-
fontSize,
|
|
1937
|
+
fontSize: fittedFontSize,
|
|
2031
1938
|
fontWeight: normalizeFontWeight(params.fontWeight),
|
|
2032
1939
|
color: params.color || '#ffffff',
|
|
2033
1940
|
textAlign: params.textAlign || 'center',
|
|
@@ -2080,17 +1987,14 @@ async function handleStateTool(name, args) {
|
|
|
2080
1987
|
}
|
|
2081
1988
|
else if (layerType === 'rectangle') {
|
|
2082
1989
|
const fillColor = normalizeHexColor(params.fillColor) || '#3b82f6';
|
|
2083
|
-
const fillOpacity = normalizeOpacity(params.fillOpacity, 1);
|
|
2084
1990
|
const strokeColor = normalizeHexColor(params.strokeColor) || '#000000';
|
|
2085
|
-
const gradientFill = buildFillFromParams(params);
|
|
2086
1991
|
layer = {
|
|
2087
1992
|
...baseLayer,
|
|
2088
1993
|
type: 'rectangle',
|
|
2089
1994
|
style: {
|
|
2090
|
-
fill: gradientFill ?? { type: 'solid', color: fillColor, opacity: fillOpacity },
|
|
2091
1995
|
fillType: 'solid',
|
|
2092
1996
|
fillColor,
|
|
2093
|
-
fillOpacity,
|
|
1997
|
+
fillOpacity: normalizeOpacity(params.fillOpacity, 1),
|
|
2094
1998
|
strokeColor,
|
|
2095
1999
|
strokeWidth: normalizeStrokeWidth(params.strokeWidth, 0),
|
|
2096
2000
|
strokeOpacity: normalizeOpacity(params.strokeOpacity, 1),
|
|
@@ -2105,19 +2009,16 @@ async function handleStateTool(name, args) {
|
|
|
2105
2009
|
}
|
|
2106
2010
|
else if (layerType === 'ellipse') {
|
|
2107
2011
|
const fillColor = normalizeHexColor(params.fillColor) || '#3b82f6';
|
|
2108
|
-
const fillOpacity = normalizeOpacity(params.fillOpacity, 1);
|
|
2109
2012
|
const strokeColor = normalizeHexColor(params.strokeColor) || '#000000';
|
|
2110
2013
|
const w = cssPercentToSize(params.width, 20);
|
|
2111
2014
|
const h = cssPercentToSize(params.height, 20);
|
|
2112
|
-
const gradientFill = buildFillFromParams(params);
|
|
2113
2015
|
layer = {
|
|
2114
2016
|
...baseLayer,
|
|
2115
2017
|
type: 'ellipse',
|
|
2116
2018
|
style: {
|
|
2117
|
-
fill: gradientFill ?? { type: 'solid', color: fillColor, opacity: fillOpacity },
|
|
2118
2019
|
fillType: 'solid',
|
|
2119
2020
|
fillColor,
|
|
2120
|
-
fillOpacity,
|
|
2021
|
+
fillOpacity: normalizeOpacity(params.fillOpacity, 1),
|
|
2121
2022
|
strokeColor,
|
|
2122
2023
|
strokeWidth: normalizeStrokeWidth(params.strokeWidth, 0),
|
|
2123
2024
|
strokeOpacity: normalizeOpacity(params.strokeOpacity, 1),
|
|
@@ -2130,19 +2031,16 @@ async function handleStateTool(name, args) {
|
|
|
2130
2031
|
}
|
|
2131
2032
|
else if (layerType === 'polygon') {
|
|
2132
2033
|
const fillColor = normalizeHexColor(params.fillColor) || '#3b82f6';
|
|
2133
|
-
const fillOpacity = normalizeOpacity(params.fillOpacity, 1);
|
|
2134
2034
|
const strokeColor = normalizeHexColor(params.strokeColor) || '#000000';
|
|
2135
2035
|
const w = cssPercentToSize(params.width, 20);
|
|
2136
2036
|
const h = cssPercentToSize(params.height, 20);
|
|
2137
|
-
const gradientFill = buildFillFromParams(params);
|
|
2138
2037
|
layer = {
|
|
2139
2038
|
...baseLayer,
|
|
2140
2039
|
type: 'polygon',
|
|
2141
2040
|
style: {
|
|
2142
|
-
fill: gradientFill ?? { type: 'solid', color: fillColor, opacity: fillOpacity },
|
|
2143
2041
|
fillType: 'solid',
|
|
2144
2042
|
fillColor,
|
|
2145
|
-
fillOpacity,
|
|
2043
|
+
fillOpacity: normalizeOpacity(params.fillOpacity, 1),
|
|
2146
2044
|
strokeColor,
|
|
2147
2045
|
strokeWidth: normalizeStrokeWidth(params.strokeWidth, 0),
|
|
2148
2046
|
strokeOpacity: normalizeOpacity(params.strokeOpacity, 1),
|
|
@@ -2156,19 +2054,16 @@ async function handleStateTool(name, args) {
|
|
|
2156
2054
|
}
|
|
2157
2055
|
else if (layerType === 'star') {
|
|
2158
2056
|
const fillColor = normalizeHexColor(params.fillColor) || '#3b82f6';
|
|
2159
|
-
const fillOpacity = normalizeOpacity(params.fillOpacity, 1);
|
|
2160
2057
|
const strokeColor = normalizeHexColor(params.strokeColor) || '#000000';
|
|
2161
2058
|
const w = cssPercentToSize(params.width, 20);
|
|
2162
2059
|
const h = cssPercentToSize(params.height, 20);
|
|
2163
|
-
const gradientFill = buildFillFromParams(params);
|
|
2164
2060
|
layer = {
|
|
2165
2061
|
...baseLayer,
|
|
2166
2062
|
type: 'star',
|
|
2167
2063
|
style: {
|
|
2168
|
-
fill: gradientFill ?? { type: 'solid', color: fillColor, opacity: fillOpacity },
|
|
2169
2064
|
fillType: 'solid',
|
|
2170
2065
|
fillColor,
|
|
2171
|
-
fillOpacity,
|
|
2066
|
+
fillOpacity: normalizeOpacity(params.fillOpacity, 1),
|
|
2172
2067
|
strokeColor,
|
|
2173
2068
|
strokeWidth: normalizeStrokeWidth(params.strokeWidth, 0),
|
|
2174
2069
|
strokeOpacity: normalizeOpacity(params.strokeOpacity, 1),
|
|
@@ -2211,7 +2106,6 @@ async function handleStateTool(name, args) {
|
|
|
2211
2106
|
layer = baseLayer;
|
|
2212
2107
|
}
|
|
2213
2108
|
currentPoster.layers.push(layer);
|
|
2214
|
-
persistState();
|
|
2215
2109
|
return {
|
|
2216
2110
|
content: [
|
|
2217
2111
|
{
|
|
@@ -2227,7 +2121,11 @@ async function handleStateTool(name, args) {
|
|
|
2227
2121
|
};
|
|
2228
2122
|
}
|
|
2229
2123
|
case 'add_group': {
|
|
2230
|
-
|
|
2124
|
+
if (!currentPoster) {
|
|
2125
|
+
return {
|
|
2126
|
+
content: [{ type: 'text', text: 'Error: No poster created. Use create_poster first.' }],
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2231
2129
|
const children = params.children;
|
|
2232
2130
|
if (!children || !Array.isArray(children) || children.length === 0) {
|
|
2233
2131
|
return {
|
|
@@ -2276,29 +2174,46 @@ async function handleStateTool(name, args) {
|
|
|
2276
2174
|
const frameHeightPx = typeof childParams.frameHeightPx === 'number' ? childParams.frameHeightPx : frameHeightPxFromNormalized;
|
|
2277
2175
|
const textBoxWidth = typeof childParams.textBoxWidth === 'number' ? childParams.textBoxWidth : undefined;
|
|
2278
2176
|
const childFontSize = childParams.fontSize || 72;
|
|
2279
|
-
// Default frameWidthPx for
|
|
2177
|
+
// Default frameWidthPx — account for canvas + group padding so the
|
|
2178
|
+
// frame width matches the actual available space after layout shrink-to-fit.
|
|
2280
2179
|
if (frameWidthPx == null && textBoxWidth == null) {
|
|
2281
|
-
const
|
|
2180
|
+
const canvasPad = currentPoster.canvas.padding || { top: 0, right: 0, bottom: 0, left: 0 };
|
|
2181
|
+
const canvasContentW = 1.0 - (canvasPad.left || 0) - (canvasPad.right || 0);
|
|
2182
|
+
const grpPadPct = params.padding ?? 0;
|
|
2183
|
+
const grpPadNorm = cssPercentToSize(grpPadPct, 0);
|
|
2184
|
+
const grpPadLR = (cssPercentToSize(params.paddingLeft ?? grpPadPct, 0)) +
|
|
2185
|
+
(cssPercentToSize(params.paddingRight ?? grpPadPct, 0));
|
|
2186
|
+
const effectiveContentW = canvasContentW - grpPadLR;
|
|
2187
|
+
const defaultFrameWidthNormalized = Math.min(childFontSize >= 72 ? 0.88 : 0.78, effectiveContentW * 0.98);
|
|
2282
2188
|
frameWidthPx = textNormalizedToPx(defaultFrameWidthNormalized, artboardAspect, 'width');
|
|
2283
2189
|
}
|
|
2190
|
+
const childContent = childParams.content || 'Text';
|
|
2191
|
+
const childLetterSpacing = childParams.letterSpacing ?? 0;
|
|
2192
|
+
const childAutoSize = childParams.autoSize
|
|
2193
|
+
? childParams.autoSize
|
|
2194
|
+
: 'height';
|
|
2195
|
+
// Auto-fit: shrink font if the longest word can't fit on one line
|
|
2196
|
+
const fittedChildFontSize = frameWidthPx != null
|
|
2197
|
+
? autoFitFontSize(childContent, childFontSize, childLetterSpacing, frameWidthPx)
|
|
2198
|
+
: childFontSize;
|
|
2284
2199
|
childLayers.push({
|
|
2285
2200
|
...childBase,
|
|
2286
2201
|
type: 'text',
|
|
2287
|
-
content:
|
|
2202
|
+
content: childContent,
|
|
2288
2203
|
textType: 'frame',
|
|
2289
2204
|
fontFamily: normalizeFontFamily(childParams.fontFamily),
|
|
2290
|
-
fontSize:
|
|
2205
|
+
fontSize: fittedChildFontSize,
|
|
2291
2206
|
fontWeight: normalizeFontWeight(childParams.fontWeight),
|
|
2292
2207
|
color: childParams.color || '#ffffff',
|
|
2293
2208
|
textAlign: childParams.textAlign || 'center',
|
|
2294
2209
|
...(childParams.verticalAlign ? { verticalAlign: childParams.verticalAlign } : {}),
|
|
2295
|
-
letterSpacing:
|
|
2210
|
+
letterSpacing: childLetterSpacing,
|
|
2296
2211
|
lineHeight: childParams.lineHeight ?? 1.2,
|
|
2297
2212
|
textTransformMode: 'box',
|
|
2298
2213
|
...(textBoxWidth != null ? { textBoxWidth } : {}),
|
|
2299
|
-
frameWidthPx,
|
|
2214
|
+
...(frameWidthPx != null ? { frameWidthPx } : {}),
|
|
2300
2215
|
...(typeof frameHeightPx === 'number' ? { frameHeightPx } : {}),
|
|
2301
|
-
|
|
2216
|
+
autoSize: childAutoSize,
|
|
2302
2217
|
...(parseTextTransform(childParams) ? { textTransform: parseTextTransform(childParams) } : {}),
|
|
2303
2218
|
...parseTextDecoration(childParams),
|
|
2304
2219
|
});
|
|
@@ -2333,17 +2248,14 @@ async function handleStateTool(name, args) {
|
|
|
2333
2248
|
}
|
|
2334
2249
|
else if (childType === 'rectangle') {
|
|
2335
2250
|
const fillColor = normalizeHexColor(childParams.fillColor) || '#3b82f6';
|
|
2336
|
-
const fillOpacity = normalizeOpacity(childParams.fillOpacity, 1);
|
|
2337
2251
|
const strokeColor = normalizeHexColor(childParams.strokeColor) || '#000000';
|
|
2338
|
-
const gradientFill = buildFillFromParams(childParams);
|
|
2339
2252
|
childLayers.push({
|
|
2340
2253
|
...childBase,
|
|
2341
2254
|
type: 'rectangle',
|
|
2342
2255
|
style: {
|
|
2343
|
-
fill: gradientFill ?? { type: 'solid', color: fillColor, opacity: fillOpacity },
|
|
2344
2256
|
fillType: 'solid',
|
|
2345
2257
|
fillColor,
|
|
2346
|
-
fillOpacity,
|
|
2258
|
+
fillOpacity: normalizeOpacity(childParams.fillOpacity, 1),
|
|
2347
2259
|
strokeColor,
|
|
2348
2260
|
strokeWidth: normalizeStrokeWidth(childParams.strokeWidth, 0),
|
|
2349
2261
|
strokeOpacity: normalizeOpacity(childParams.strokeOpacity, 1),
|
|
@@ -2358,19 +2270,16 @@ async function handleStateTool(name, args) {
|
|
|
2358
2270
|
}
|
|
2359
2271
|
else if (childType === 'ellipse') {
|
|
2360
2272
|
const fillColor = normalizeHexColor(childParams.fillColor) || '#3b82f6';
|
|
2361
|
-
const fillOpacity = normalizeOpacity(childParams.fillOpacity, 1);
|
|
2362
2273
|
const strokeColor = normalizeHexColor(childParams.strokeColor) || '#000000';
|
|
2363
2274
|
const w = cssPercentToSize(childParams.width, 20);
|
|
2364
2275
|
const h = cssPercentToSize(childParams.height, 20);
|
|
2365
|
-
const gradientFill = buildFillFromParams(childParams);
|
|
2366
2276
|
childLayers.push({
|
|
2367
2277
|
...childBase,
|
|
2368
2278
|
type: 'ellipse',
|
|
2369
2279
|
style: {
|
|
2370
|
-
fill: gradientFill ?? { type: 'solid', color: fillColor, opacity: fillOpacity },
|
|
2371
2280
|
fillType: 'solid',
|
|
2372
2281
|
fillColor,
|
|
2373
|
-
fillOpacity,
|
|
2282
|
+
fillOpacity: normalizeOpacity(childParams.fillOpacity, 1),
|
|
2374
2283
|
strokeColor,
|
|
2375
2284
|
strokeWidth: normalizeStrokeWidth(childParams.strokeWidth, 0),
|
|
2376
2285
|
strokeOpacity: normalizeOpacity(childParams.strokeOpacity, 1),
|
|
@@ -2383,19 +2292,16 @@ async function handleStateTool(name, args) {
|
|
|
2383
2292
|
}
|
|
2384
2293
|
else if (childType === 'polygon') {
|
|
2385
2294
|
const fillColor = normalizeHexColor(childParams.fillColor) || '#3b82f6';
|
|
2386
|
-
const fillOpacity = normalizeOpacity(childParams.fillOpacity, 1);
|
|
2387
2295
|
const strokeColor = normalizeHexColor(childParams.strokeColor) || '#000000';
|
|
2388
2296
|
const w = cssPercentToSize(childParams.width, 20);
|
|
2389
2297
|
const h = cssPercentToSize(childParams.height, 20);
|
|
2390
|
-
const gradientFill = buildFillFromParams(childParams);
|
|
2391
2298
|
childLayers.push({
|
|
2392
2299
|
...childBase,
|
|
2393
2300
|
type: 'polygon',
|
|
2394
2301
|
style: {
|
|
2395
|
-
fill: gradientFill ?? { type: 'solid', color: fillColor, opacity: fillOpacity },
|
|
2396
2302
|
fillType: 'solid',
|
|
2397
2303
|
fillColor,
|
|
2398
|
-
fillOpacity,
|
|
2304
|
+
fillOpacity: normalizeOpacity(childParams.fillOpacity, 1),
|
|
2399
2305
|
strokeColor,
|
|
2400
2306
|
strokeWidth: normalizeStrokeWidth(childParams.strokeWidth, 0),
|
|
2401
2307
|
strokeOpacity: normalizeOpacity(childParams.strokeOpacity, 1),
|
|
@@ -2409,19 +2315,16 @@ async function handleStateTool(name, args) {
|
|
|
2409
2315
|
}
|
|
2410
2316
|
else if (childType === 'star') {
|
|
2411
2317
|
const fillColor = normalizeHexColor(childParams.fillColor) || '#3b82f6';
|
|
2412
|
-
const fillOpacity = normalizeOpacity(childParams.fillOpacity, 1);
|
|
2413
2318
|
const strokeColor = normalizeHexColor(childParams.strokeColor) || '#000000';
|
|
2414
2319
|
const w = cssPercentToSize(childParams.width, 20);
|
|
2415
2320
|
const h = cssPercentToSize(childParams.height, 20);
|
|
2416
|
-
const gradientFill = buildFillFromParams(childParams);
|
|
2417
2321
|
childLayers.push({
|
|
2418
2322
|
...childBase,
|
|
2419
2323
|
type: 'star',
|
|
2420
2324
|
style: {
|
|
2421
|
-
fill: gradientFill ?? { type: 'solid', color: fillColor, opacity: fillOpacity },
|
|
2422
2325
|
fillType: 'solid',
|
|
2423
2326
|
fillColor,
|
|
2424
|
-
fillOpacity,
|
|
2327
|
+
fillOpacity: normalizeOpacity(childParams.fillOpacity, 1),
|
|
2425
2328
|
strokeColor,
|
|
2426
2329
|
strokeWidth: normalizeStrokeWidth(childParams.strokeWidth, 0),
|
|
2427
2330
|
strokeOpacity: normalizeOpacity(childParams.strokeOpacity, 1),
|
|
@@ -2511,7 +2414,6 @@ async function handleStateTool(name, args) {
|
|
|
2511
2414
|
...(params.cornerRadius !== undefined ? { cornerRadius: params.cornerRadius } : {}),
|
|
2512
2415
|
};
|
|
2513
2416
|
currentPoster.layers.push(groupLayer);
|
|
2514
|
-
persistState();
|
|
2515
2417
|
return {
|
|
2516
2418
|
content: [
|
|
2517
2419
|
{
|
|
@@ -2527,7 +2429,11 @@ async function handleStateTool(name, args) {
|
|
|
2527
2429
|
};
|
|
2528
2430
|
}
|
|
2529
2431
|
case 'modify_layer': {
|
|
2530
|
-
|
|
2432
|
+
if (!currentPoster) {
|
|
2433
|
+
return {
|
|
2434
|
+
content: [{ type: 'text', text: 'Error: No poster created. Use create_poster first.' }],
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2531
2437
|
const layerId = params.layerId;
|
|
2532
2438
|
if (!layerId) {
|
|
2533
2439
|
return {
|
|
@@ -2875,19 +2781,6 @@ async function handleStateTool(name, args) {
|
|
|
2875
2781
|
const existing = asFiniteNumber(anyLayer.style.strokeOpacity, 1);
|
|
2876
2782
|
anyLayer.style.strokeOpacity = normalizeOpacity(params.strokeOpacity, existing);
|
|
2877
2783
|
}
|
|
2878
|
-
// Gradient fill update
|
|
2879
|
-
const modifyGradientFill = buildFillFromParams(params);
|
|
2880
|
-
if (modifyGradientFill) {
|
|
2881
|
-
anyLayer.style.fill = modifyGradientFill;
|
|
2882
|
-
anyLayer.style.fillType = 'solid';
|
|
2883
|
-
}
|
|
2884
|
-
else if (params.fillColor !== undefined || params.fillOpacity !== undefined) {
|
|
2885
|
-
// Sync style.fill as solid when only color/opacity changed
|
|
2886
|
-
const currentFillColor = typeof anyLayer.style.fillColor === 'string' ? anyLayer.style.fillColor : '#3b82f6';
|
|
2887
|
-
const currentFillOpacity = asFiniteNumber(anyLayer.style.fillOpacity, 1);
|
|
2888
|
-
anyLayer.style.fill = { type: 'solid', color: currentFillColor, opacity: currentFillOpacity };
|
|
2889
|
-
anyLayer.style.fillType = 'solid';
|
|
2890
|
-
}
|
|
2891
2784
|
if (anyLayer.style.fillType === undefined && layer.type !== 'line')
|
|
2892
2785
|
anyLayer.style.fillType = 'solid';
|
|
2893
2786
|
if (anyLayer.style.strokeCap === undefined)
|
|
@@ -2967,7 +2860,6 @@ async function handleStateTool(name, args) {
|
|
|
2967
2860
|
layer.transform.height = 1;
|
|
2968
2861
|
}
|
|
2969
2862
|
}
|
|
2970
|
-
persistState();
|
|
2971
2863
|
return {
|
|
2972
2864
|
content: [
|
|
2973
2865
|
{
|
|
@@ -2982,7 +2874,11 @@ async function handleStateTool(name, args) {
|
|
|
2982
2874
|
};
|
|
2983
2875
|
}
|
|
2984
2876
|
case 'apply_effect': {
|
|
2985
|
-
|
|
2877
|
+
if (!currentPoster) {
|
|
2878
|
+
return {
|
|
2879
|
+
content: [{ type: 'text', text: 'Error: No poster created. Use create_poster first.' }],
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2986
2882
|
const effectId = params.effectId;
|
|
2987
2883
|
if (!effectId) {
|
|
2988
2884
|
return {
|
|
@@ -2991,7 +2887,6 @@ async function handleStateTool(name, args) {
|
|
|
2991
2887
|
}
|
|
2992
2888
|
if (effectId === 'none') {
|
|
2993
2889
|
delete currentPoster.effect;
|
|
2994
|
-
persistState();
|
|
2995
2890
|
return {
|
|
2996
2891
|
content: [
|
|
2997
2892
|
{
|
|
@@ -3239,7 +3134,6 @@ async function handleStateTool(name, args) {
|
|
|
3239
3134
|
};
|
|
3240
3135
|
}
|
|
3241
3136
|
currentPoster.effect = effect;
|
|
3242
|
-
persistState();
|
|
3243
3137
|
return {
|
|
3244
3138
|
content: [
|
|
3245
3139
|
{
|
|
@@ -3254,7 +3148,11 @@ async function handleStateTool(name, args) {
|
|
|
3254
3148
|
};
|
|
3255
3149
|
}
|
|
3256
3150
|
case 'add_postprocess': {
|
|
3257
|
-
|
|
3151
|
+
if (!currentPoster) {
|
|
3152
|
+
return {
|
|
3153
|
+
content: [{ type: 'text', text: 'Error: No poster created. Use create_poster first.' }],
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3258
3156
|
const ppType = params.type;
|
|
3259
3157
|
if (!ppType) {
|
|
3260
3158
|
return {
|
|
@@ -3336,7 +3234,6 @@ async function handleStateTool(name, args) {
|
|
|
3336
3234
|
settings,
|
|
3337
3235
|
};
|
|
3338
3236
|
currentPoster.postProcesses.push(postProcess);
|
|
3339
|
-
persistState();
|
|
3340
3237
|
// Build response with warning if dither is active
|
|
3341
3238
|
const response = {
|
|
3342
3239
|
success: true,
|
|
@@ -3359,7 +3256,11 @@ async function handleStateTool(name, args) {
|
|
|
3359
3256
|
};
|
|
3360
3257
|
}
|
|
3361
3258
|
case 'remove_layer': {
|
|
3362
|
-
|
|
3259
|
+
if (!currentPoster) {
|
|
3260
|
+
return {
|
|
3261
|
+
content: [{ type: 'text', text: 'Error: No poster created. Use create_poster first.' }],
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3363
3264
|
const layerId = params.layerId;
|
|
3364
3265
|
if (!layerId) {
|
|
3365
3266
|
return {
|
|
@@ -3378,7 +3279,6 @@ async function handleStateTool(name, args) {
|
|
|
3378
3279
|
};
|
|
3379
3280
|
}
|
|
3380
3281
|
currentPoster.layers.splice(layerIndex, 1);
|
|
3381
|
-
persistState();
|
|
3382
3282
|
return {
|
|
3383
3283
|
content: [
|
|
3384
3284
|
{
|
|
@@ -3393,7 +3293,11 @@ async function handleStateTool(name, args) {
|
|
|
3393
3293
|
};
|
|
3394
3294
|
}
|
|
3395
3295
|
case 'move_layer': {
|
|
3396
|
-
|
|
3296
|
+
if (!currentPoster) {
|
|
3297
|
+
return {
|
|
3298
|
+
content: [{ type: 'text', text: 'Error: No poster created. Use create_poster first.' }],
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3397
3301
|
const poster = currentPoster;
|
|
3398
3302
|
const layerId = params.layerId;
|
|
3399
3303
|
const direction = params.direction;
|
|
@@ -3436,7 +3340,6 @@ async function handleStateTool(name, args) {
|
|
|
3436
3340
|
content: [{ type: 'text', text: 'Error: direction must be one of: up, down, top, bottom' }],
|
|
3437
3341
|
};
|
|
3438
3342
|
}
|
|
3439
|
-
persistState();
|
|
3440
3343
|
return {
|
|
3441
3344
|
content: [
|
|
3442
3345
|
{
|
|
@@ -3452,7 +3355,11 @@ async function handleStateTool(name, args) {
|
|
|
3452
3355
|
};
|
|
3453
3356
|
}
|
|
3454
3357
|
case 'get_state': {
|
|
3455
|
-
|
|
3358
|
+
if (!currentPoster) {
|
|
3359
|
+
return {
|
|
3360
|
+
content: [{ type: 'text', text: 'No poster created yet. Use create_poster first.' }],
|
|
3361
|
+
};
|
|
3362
|
+
}
|
|
3456
3363
|
return {
|
|
3457
3364
|
content: [
|
|
3458
3365
|
{
|