Skip to content

Commit e12e3cb

Browse files
authored
Get renderable item details (#2)
* Add renderable item details tool * Update types * Tool description updates * Add preview link for designs * Check if layer has parametrization * Review updates * Add default value
1 parent a0774aa commit e12e3cb

File tree

8 files changed

+425
-52
lines changed

8 files changed

+425
-52
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { createAxiosInstance } from "../../axiosConfig";
2+
import {
3+
DesignDetails,
4+
DesignVariant,
5+
Layer,
6+
ProjectDetails,
7+
RenderableItemDetails,
8+
RenderableItemParameterType,
9+
} from "../types";
10+
11+
const api = createAxiosInstance();
12+
13+
export const getRenderableItemsDetails = async (
14+
renderableItemId: string,
15+
isDesign: boolean
16+
): Promise<RenderableItemDetails[]> => {
17+
if (isDesign) {
18+
return await getDesignVariants(renderableItemId);
19+
} else {
20+
return await getProjectTemplates(renderableItemId);
21+
}
22+
};
23+
24+
const getDesignVariants = async (
25+
designId: string
26+
): Promise<RenderableItemDetails[]> => {
27+
const response = await api.get<DesignDetails>(`/api/v2/designs/${designId}`);
28+
const designDetails = response.data;
29+
30+
// flatten variants into renderable items with parameter details
31+
return designDetails.variants.map((variant) => ({
32+
isDesign: true,
33+
projectDesignId: designDetails.id,
34+
templateVariantId: variant.id,
35+
exampleVideoUrl: getExampleVideoUrl(variant),
36+
parameters: designDetails.parameters.map((param) => ({
37+
key: param.key,
38+
mandatory: !param.optional,
39+
type: getDesignParameterType(param.type),
40+
description: param.description,
41+
label: null,
42+
defaultValue: param.defaultValue || null,
43+
})),
44+
}));
45+
};
46+
47+
const getProjectTemplates = async (
48+
projectId: string
49+
): Promise<RenderableItemDetails[]> => {
50+
const response = await api.get<ProjectDetails>(
51+
`/api/v2/projects/${projectId}`
52+
);
53+
const projectDetails = response.data;
54+
55+
// flatten templates into renderable items with parameter details
56+
return projectDetails.templates.map((template) => ({
57+
isDesign: false,
58+
projectDesignId: projectDetails.id,
59+
templateVariantId: template.id,
60+
parameters: template.layers.filter(isLayerParametrized).map((layer) => ({
61+
key: layer.parametrization.value.replace("#", ""),
62+
mandatory: layer.parametrization.mandatory,
63+
type: getProjectParameterType(layer),
64+
description: null,
65+
label: layer.label || null,
66+
defaultValue: layer.parametrization.defaultValue || null,
67+
})),
68+
}));
69+
};
70+
71+
const isLayerParametrized = (
72+
layer: Layer
73+
): layer is Layer & {
74+
parametrization: { value: string; mandatory: boolean };
75+
} => {
76+
return layer.parametrization != null;
77+
};
78+
79+
const getExampleVideoUrl = (variant: DesignVariant): string | undefined => {
80+
const allVariantExamples = Object.values(variant.examples || {});
81+
if (allVariantExamples.length === 0) {
82+
return undefined;
83+
}
84+
85+
// Find the first example with a videoUrl
86+
const videoExample = allVariantExamples.find((example) => example.videoUrl);
87+
if (videoExample) {
88+
return videoExample.videoUrl;
89+
}
90+
91+
return undefined;
92+
};
93+
94+
const getDesignParameterType = (type: string): RenderableItemParameterType => {
95+
switch (type) {
96+
case "STRING":
97+
return "STRING";
98+
case "MEDIA":
99+
return "MEDIA"; // designs do not specify media type
100+
case "COLOR":
101+
return "COLOR";
102+
default:
103+
return "STRING"; // Fallback to STRING if unknown
104+
}
105+
};
106+
107+
const getProjectParameterType = (layer: Layer): RenderableItemParameterType => {
108+
switch (layer.layerType) {
109+
case "DATA":
110+
return "STRING";
111+
case "MEDIA":
112+
return `MEDIA (${layer.mediaType})`; // specify media type
113+
case "SOLID_COLOR":
114+
return "COLOR";
115+
default:
116+
return "STRING"; // Fallback to STRING if unknown
117+
}
118+
};

src/sdk/api/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./listRenderableItems";
2+
export * from "./getRenderableItemDetails";
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { createAxiosInstance } from "../axiosConfig";
2-
import { getAspectRatio } from "../utils/aspectRatio";
3-
import { Design, Project, RenderableItem } from "./types";
1+
import { createAxiosInstance } from "../../axiosConfig";
2+
import { getAspectRatio } from "../../utils/aspectRatio";
3+
import { Design, Project, RenderableItem } from "../types";
44

55
const api = createAxiosInstance();
66

src/sdk/types.ts

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type DesignVariant = {
55
name: string;
66
aspectRatio: AspectRatio;
77
duration: number;
8+
examples: { [key: string]: { videoUrl: string } };
89
};
910

1011
export type ProjectTemplate = {
@@ -17,16 +18,16 @@ export type ProjectTemplate = {
1718
};
1819
};
1920

20-
export type Design = {
21+
export type Design<V extends DesignVariant = DesignVariant> = {
2122
id: string;
2223
name: string;
2324
description: string;
2425
category: string;
2526
renderUiDisabled: boolean;
26-
variants: DesignVariant[];
27+
variants: V[];
2728
};
2829

29-
export type Project = {
30+
export type Project<T extends ProjectTemplate = ProjectTemplate> = {
3031
id: string;
3132
name: string;
3233
description: string | null;
@@ -35,7 +36,7 @@ export type Project = {
3536
tags?: string[];
3637
folder?: string;
3738
};
38-
templates: ProjectTemplate[];
39+
templates: T[];
3940
};
4041

4142
export type RenderableTemplate = {
@@ -56,3 +57,86 @@ export type RenderableItem = {
5657
};
5758
templates: RenderableTemplate[];
5859
};
60+
61+
export type ParameterType =
62+
| "STRING"
63+
| "MEDIA"
64+
| "COLOR"
65+
| "NUMBER"
66+
| "BOOLEAN"
67+
| "COMPLEX";
68+
69+
export type DesignVariantDetails = DesignVariant & {};
70+
71+
export type DesignDetails = Design<DesignVariantDetails> & {
72+
parameters: {
73+
key: string;
74+
type: ParameterType;
75+
name: string;
76+
description: string;
77+
optional: boolean;
78+
defaultValue: any;
79+
sampleValue: any;
80+
}[];
81+
};
82+
83+
enum LayerType {
84+
DATA = "DATA",
85+
MEDIA = "MEDIA",
86+
SOLID_COLOR = "SOLID_COLOR",
87+
}
88+
89+
type Parametrization = {
90+
value: string;
91+
defaultValue?: string;
92+
expression: boolean;
93+
mandatory: boolean;
94+
};
95+
96+
type AbstractLayer<T extends LayerType> = {
97+
layerType: T;
98+
internalId: string;
99+
label?: string;
100+
layerName: string;
101+
parametrization?: Parametrization;
102+
};
103+
104+
type DataLayer = AbstractLayer<LayerType.DATA>;
105+
106+
type MediaLayer = AbstractLayer<LayerType.MEDIA> & {
107+
mediaType: "image" | "video" | "audio";
108+
};
109+
110+
type SolidColorLayer = AbstractLayer<LayerType.SOLID_COLOR>;
111+
112+
export type Layer = DataLayer | MediaLayer | SolidColorLayer;
113+
114+
export type ProjectTemplateDetails = ProjectTemplate & {
115+
layers: Layer[];
116+
};
117+
118+
export type ProjectDetails = Project<ProjectTemplateDetails> & {};
119+
120+
export type RenderableItemParameterType =
121+
| "STRING"
122+
| "MEDIA"
123+
| "MEDIA (image)"
124+
| "MEDIA (audio)"
125+
| "MEDIA (video)"
126+
| "COLOR";
127+
128+
export type RenderableItemDetails = {
129+
isDesign: boolean;
130+
projectDesignId: string;
131+
templateVariantId: string;
132+
exampleVideoUrl?: string;
133+
parameters: {
134+
key: string;
135+
mandatory: boolean;
136+
// Used to understand what parameter changes in the video
137+
type: RenderableItemParameterType;
138+
description: string | null;
139+
label: string | null;
140+
defaultValue?: any | null;
141+
}[];
142+
};

src/server.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3-
import { registerListRenderableItems } from "./tools/listRenderableItems";
3+
import {
4+
registerListRenderableItems,
5+
registerGetRenderableItemDetails,
6+
} from "./tools";
47

58
export class PlainlyMcpServer {
69
server: McpServer;
@@ -15,6 +18,7 @@ export class PlainlyMcpServer {
1518

1619
// Register tools
1720
registerListRenderableItems(this.server);
21+
registerGetRenderableItemDetails(this.server);
1822
}
1923

2024
async start() {

0 commit comments

Comments
 (0)