mulmocast 2.2.5 → 2.3.0
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 +45 -0
- package/lib/cli/commands/tool/info/builder.d.ts +2 -0
- package/lib/cli/commands/tool/info/builder.js +6 -1
- package/lib/cli/commands/tool/info/handler.d.ts +1 -0
- package/lib/cli/commands/tool/info/handler.js +84 -3
- package/lib/cli/commands/tool/info/index.d.ts +1 -1
- package/lib/cli/commands/tool/info/index.js +1 -1
- package/lib/slide/index.d.ts +3 -2
- package/lib/slide/index.js +1 -1
- package/lib/slide/render.d.ts +15 -1
- package/lib/slide/render.js +45 -3
- package/lib/slide/schema.d.ts +103 -0
- package/lib/slide/schema.js +32 -0
- package/lib/tools/complete_script.d.ts +2 -1
- package/lib/tools/complete_script.js +11 -5
- package/lib/types/schema.d.ts +410 -0
- package/lib/types/schema.js +2 -1
- package/lib/types/slide.d.ts +103 -0
- package/lib/types/slide.js +32 -0
- package/lib/utils/context.d.ts +155 -0
- package/lib/utils/context.js +6 -2
- package/lib/utils/image_plugins/slide.js +52 -6
- package/lib/utils/mulmo_config.d.ts +28 -0
- package/lib/utils/mulmo_config.js +90 -0
- package/package.json +10 -10
- package/scripts/test/branding/banner.jpg +0 -0
- package/scripts/test/branding/logo.svg +3 -0
- package/scripts/test/test_media.json +17 -0
- package/scripts/test/test_slide_branding.json +107 -0
package/README.md
CHANGED
|
@@ -207,6 +207,51 @@ MulmoScript configuration (same as OpenAI):
|
|
|
207
207
|
|
|
208
208
|
For detailed setup and region availability, see [Azure OpenAI Usage Guide](./docs/azure_openai_usage.md).
|
|
209
209
|
|
|
210
|
+
### mulmo.config.json
|
|
211
|
+
|
|
212
|
+
Create a `mulmo.config.json` file to set project-wide defaults. The CLI searches for it in **CWD** first, then **home directory (`~/`)**.
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"speechParams": {
|
|
217
|
+
"provider": "elevenlabs"
|
|
218
|
+
},
|
|
219
|
+
"imageParams": {
|
|
220
|
+
"provider": "google"
|
|
221
|
+
},
|
|
222
|
+
"audioParams": {
|
|
223
|
+
"bgm": { "kind": "path", "path": "assets/bgm.mp3" },
|
|
224
|
+
"bgmVolume": 0.15
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Top-level keys are applied as **defaults** (script values take precedence). Use the `override` key to **force** values over scripts — useful for enterprise branding or TTS provider enforcement:
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"speechParams": {
|
|
234
|
+
"provider": "elevenlabs"
|
|
235
|
+
},
|
|
236
|
+
"override": {
|
|
237
|
+
"speechParams": {
|
|
238
|
+
"provider": "elevenlabs",
|
|
239
|
+
"model": "eleven_multilingual_v2",
|
|
240
|
+
"speakers": {
|
|
241
|
+
"Presenter": { "provider": "elevenlabs", "voiceId": "Rachel" }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Priority chain: `config (defaults)` < `template/style` < `script` < `config.override` < `presentationStyle (-p)`
|
|
249
|
+
|
|
250
|
+
Verify the merged result with:
|
|
251
|
+
```bash
|
|
252
|
+
mulmo tool info merged --script <script.json>
|
|
253
|
+
```
|
|
254
|
+
|
|
210
255
|
## Workflow
|
|
211
256
|
|
|
212
257
|
1. Create a MulmoScript JSON file with `mulmo tool scripting`
|
|
@@ -2,7 +2,7 @@ export const builder = (yargs) => yargs
|
|
|
2
2
|
.positional("category", {
|
|
3
3
|
describe: "Category to show info for",
|
|
4
4
|
type: "string",
|
|
5
|
-
choices: ["styles", "bgm", "templates", "voices", "images", "movies", "llm", "themes"],
|
|
5
|
+
choices: ["styles", "bgm", "templates", "voices", "images", "movies", "llm", "themes", "config", "merged"],
|
|
6
6
|
})
|
|
7
7
|
.option("format", {
|
|
8
8
|
alias: "F",
|
|
@@ -10,4 +10,9 @@ export const builder = (yargs) => yargs
|
|
|
10
10
|
type: "string",
|
|
11
11
|
choices: ["text", "json", "yaml"],
|
|
12
12
|
default: "text",
|
|
13
|
+
})
|
|
14
|
+
.option("script", {
|
|
15
|
+
alias: "S",
|
|
16
|
+
describe: "Script file path (required for 'merged' category)",
|
|
17
|
+
type: "string",
|
|
13
18
|
});
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
+
import path from "path";
|
|
2
3
|
import { getMarkdownStyleNames, getMarkdownCategories, getMarkdownStylesByCategory } from "../../../../data/markdownStyles.js";
|
|
3
4
|
import { bgmAssets } from "../../../../data/bgmAssets.js";
|
|
4
5
|
import { templateDataSet } from "../../../../data/templateDataSet.js";
|
|
5
6
|
import { slideThemes } from "../../../../data/slideThemes.js";
|
|
6
7
|
import { provider2TTSAgent, provider2ImageAgent, provider2MovieAgent, provider2LLMAgent } from "../../../../types/provider2agent.js";
|
|
8
|
+
import { findConfigFile, loadMulmoConfig, mergeConfigWithScript } from "../../../../utils/mulmo_config.js";
|
|
9
|
+
import { readMulmoScriptFile } from "../../../../utils/file.js";
|
|
7
10
|
import YAML from "yaml";
|
|
8
11
|
const formatOutput = (data, format) => {
|
|
9
12
|
if (format === "json") {
|
|
@@ -170,6 +173,78 @@ const printThemesText = () => {
|
|
|
170
173
|
}
|
|
171
174
|
console.log("");
|
|
172
175
|
};
|
|
176
|
+
const getConfigInfo = () => {
|
|
177
|
+
const baseDirPath = process.cwd();
|
|
178
|
+
const configPath = findConfigFile(baseDirPath);
|
|
179
|
+
if (!configPath) {
|
|
180
|
+
return { configFile: null, config: null };
|
|
181
|
+
}
|
|
182
|
+
const config = loadMulmoConfig(baseDirPath);
|
|
183
|
+
return { configFile: configPath, config };
|
|
184
|
+
};
|
|
185
|
+
const printConfigText = () => {
|
|
186
|
+
const baseDirPath = process.cwd();
|
|
187
|
+
const configPath = findConfigFile(baseDirPath);
|
|
188
|
+
console.log("\n📄 mulmo.config.json\n");
|
|
189
|
+
if (!configPath) {
|
|
190
|
+
console.log(" No mulmo.config.json found.");
|
|
191
|
+
console.log(" Searched: CWD → ~\n");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
console.log(` Active config: ${configPath}\n`);
|
|
195
|
+
const config = loadMulmoConfig(baseDirPath);
|
|
196
|
+
if (config) {
|
|
197
|
+
console.log(JSON.stringify(config, null, 2));
|
|
198
|
+
}
|
|
199
|
+
console.log("");
|
|
200
|
+
};
|
|
201
|
+
const readScriptFile = (scriptPath) => {
|
|
202
|
+
const result = readMulmoScriptFile(scriptPath, `Error: File not found: ${scriptPath}`);
|
|
203
|
+
if (!result) {
|
|
204
|
+
console.error(`Error: Could not read script file: ${scriptPath}`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
return result.mulmoData;
|
|
208
|
+
};
|
|
209
|
+
const getMergedInfo = (scriptPath) => {
|
|
210
|
+
if (!scriptPath) {
|
|
211
|
+
console.error("Error: --script <file> is required for 'merged' category");
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
const baseDirPath = process.cwd();
|
|
215
|
+
const configResult = loadMulmoConfig(baseDirPath);
|
|
216
|
+
const script = readScriptFile(scriptPath);
|
|
217
|
+
if (!configResult) {
|
|
218
|
+
return { configFile: null, merged: script };
|
|
219
|
+
}
|
|
220
|
+
const configPath = findConfigFile(baseDirPath);
|
|
221
|
+
const merged = mergeConfigWithScript(configResult, script);
|
|
222
|
+
return { configFile: configPath, defaults: configResult.defaults, override: configResult.override, merged };
|
|
223
|
+
};
|
|
224
|
+
const printMergedText = (scriptPath) => {
|
|
225
|
+
if (!scriptPath) {
|
|
226
|
+
console.error("Error: --script <file> is required for 'merged' category");
|
|
227
|
+
console.error("Usage: mulmo tool info merged --script <script.json>");
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
const baseDirPath = process.cwd();
|
|
231
|
+
const configResult = loadMulmoConfig(baseDirPath);
|
|
232
|
+
const script = readScriptFile(scriptPath);
|
|
233
|
+
console.log("\n📋 Merged Script Result\n");
|
|
234
|
+
console.log(` Script: ${path.resolve(scriptPath)}`);
|
|
235
|
+
if (!configResult) {
|
|
236
|
+
console.log(" Config: (none)\n");
|
|
237
|
+
console.log(JSON.stringify(script, null, 2));
|
|
238
|
+
console.log("");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const configPath = findConfigFile(baseDirPath);
|
|
242
|
+
console.log(` Config: ${configPath}`);
|
|
243
|
+
console.log(` Override: ${configResult.override ? "yes" : "no"}\n`);
|
|
244
|
+
const merged = mergeConfigWithScript(configResult, script);
|
|
245
|
+
console.log(JSON.stringify(merged, null, 2));
|
|
246
|
+
console.log("");
|
|
247
|
+
};
|
|
173
248
|
const printAllCategories = () => {
|
|
174
249
|
console.log("\n📚 Available Info Categories\n");
|
|
175
250
|
console.log(" Usage: mulmo tool info <category> [--format json|yaml]\n");
|
|
@@ -181,14 +256,16 @@ const printAllCategories = () => {
|
|
|
181
256
|
console.log(" images - Image generation providers and models");
|
|
182
257
|
console.log(" movies - Movie generation providers and models");
|
|
183
258
|
console.log(" llm - LLM providers and models");
|
|
184
|
-
console.log(" themes - Slide themes and color palettes
|
|
259
|
+
console.log(" themes - Slide themes and color palettes");
|
|
260
|
+
console.log(" config - Active mulmo.config.json location and contents");
|
|
261
|
+
console.log(" merged - Show script merged with mulmo.config.json (--script <file>)\n");
|
|
185
262
|
};
|
|
186
|
-
const validCategories = ["styles", "bgm", "templates", "voices", "images", "movies", "llm", "themes"];
|
|
263
|
+
const validCategories = ["styles", "bgm", "templates", "voices", "images", "movies", "llm", "themes", "config", "merged"];
|
|
187
264
|
const isValidCategory = (category) => {
|
|
188
265
|
return validCategories.includes(category);
|
|
189
266
|
};
|
|
190
267
|
export const handler = (argv) => {
|
|
191
|
-
const { category, format = "text" } = argv;
|
|
268
|
+
const { category, format = "text", script: scriptPath } = argv;
|
|
192
269
|
if (!category) {
|
|
193
270
|
if (format === "text") {
|
|
194
271
|
printAllCategories();
|
|
@@ -216,6 +293,8 @@ export const handler = (argv) => {
|
|
|
216
293
|
movies: getMoviesInfo,
|
|
217
294
|
llm: getLlmInfo,
|
|
218
295
|
themes: getThemesInfo,
|
|
296
|
+
config: getConfigInfo,
|
|
297
|
+
merged: () => getMergedInfo(scriptPath),
|
|
219
298
|
};
|
|
220
299
|
const textPrinters = {
|
|
221
300
|
styles: printStylesText,
|
|
@@ -226,6 +305,8 @@ export const handler = (argv) => {
|
|
|
226
305
|
movies: printMoviesText,
|
|
227
306
|
llm: printLlmText,
|
|
228
307
|
themes: printThemesText,
|
|
308
|
+
config: printConfigText,
|
|
309
|
+
merged: () => printMergedText(scriptPath),
|
|
229
310
|
};
|
|
230
311
|
if (format === "text") {
|
|
231
312
|
textPrinters[category]();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const command = "info [category]";
|
|
2
|
-
export declare const desc = "Show available options (styles, bgm, templates, voices, images, movies, llm, themes)";
|
|
2
|
+
export declare const desc = "Show available options (styles, bgm, templates, voices, images, movies, llm, themes, config, merged)";
|
|
3
3
|
export { builder } from "./builder.js";
|
|
4
4
|
export { handler } from "./handler.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export const command = "info [category]";
|
|
2
|
-
export const desc = "Show available options (styles, bgm, templates, voices, images, movies, llm, themes)";
|
|
2
|
+
export const desc = "Show available options (styles, bgm, templates, voices, images, movies, llm, themes, config, merged)";
|
|
3
3
|
export { builder } from "./builder.js";
|
|
4
4
|
export { handler } from "./handler.js";
|
package/lib/slide/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { generateSlideHTML } from "./render.js";
|
|
2
|
+
export type { ResolvedBranding } from "./render.js";
|
|
2
3
|
export { renderSlideContent } from "./layouts/index.js";
|
|
3
4
|
export { renderContentBlock, renderContentBlocks } from "./blocks.js";
|
|
4
|
-
export { mulmoSlideMediaSchema, slideLayoutSchema, slideThemeSchema, contentBlockSchema, imageRefBlockSchema, chartBlockSchema, mermaidBlockSchema, accentColorKeySchema, } from "./schema.js";
|
|
5
|
-
export type { MulmoSlideMedia, SlideLayout, SlideTheme, SlideThemeColors, SlideThemeFonts, ContentBlock, ImageRefBlock, ChartBlock, MermaidBlock, AccentColorKey, TitleSlide, ColumnsSlide, ComparisonSlide, GridSlide, BigQuoteSlide, StatsSlide, TimelineSlide, SplitSlide, MatrixSlide, TableSlide, FunnelSlide, Card, CalloutBar, SlideStyle, } from "./schema.js";
|
|
5
|
+
export { mulmoSlideMediaSchema, slideLayoutSchema, slideThemeSchema, contentBlockSchema, imageRefBlockSchema, chartBlockSchema, mermaidBlockSchema, accentColorKeySchema, slideBrandingLogoSchema, slideBrandingSchema, } from "./schema.js";
|
|
6
|
+
export type { MulmoSlideMedia, SlideLayout, SlideTheme, SlideThemeColors, SlideThemeFonts, ContentBlock, ImageRefBlock, ChartBlock, MermaidBlock, AccentColorKey, TitleSlide, ColumnsSlide, ComparisonSlide, GridSlide, BigQuoteSlide, StatsSlide, TimelineSlide, SplitSlide, MatrixSlide, TableSlide, FunnelSlide, Card, CalloutBar, SlideStyle, SlideBrandingLogo, SlideBranding, } from "./schema.js";
|
package/lib/slide/index.js
CHANGED
|
@@ -4,4 +4,4 @@ export { generateSlideHTML } from "./render.js";
|
|
|
4
4
|
export { renderSlideContent } from "./layouts/index.js";
|
|
5
5
|
export { renderContentBlock, renderContentBlocks } from "./blocks.js";
|
|
6
6
|
// Schemas
|
|
7
|
-
export { mulmoSlideMediaSchema, slideLayoutSchema, slideThemeSchema, contentBlockSchema, imageRefBlockSchema, chartBlockSchema, mermaidBlockSchema, accentColorKeySchema, } from "./schema.js";
|
|
7
|
+
export { mulmoSlideMediaSchema, slideLayoutSchema, slideThemeSchema, contentBlockSchema, imageRefBlockSchema, chartBlockSchema, mermaidBlockSchema, accentColorKeySchema, slideBrandingLogoSchema, slideBrandingSchema, } from "./schema.js";
|
package/lib/slide/render.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
import type { SlideTheme, SlideLayout } from "./schema.js";
|
|
2
|
+
/** Pre-resolved branding data (all sources converted to data URLs) */
|
|
3
|
+
export type ResolvedBranding = {
|
|
4
|
+
logo?: {
|
|
5
|
+
dataUrl: string;
|
|
6
|
+
position: string;
|
|
7
|
+
width: number;
|
|
8
|
+
};
|
|
9
|
+
backgroundImage?: {
|
|
10
|
+
dataUrl: string;
|
|
11
|
+
size: string;
|
|
12
|
+
opacity: number;
|
|
13
|
+
bgOpacity?: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
2
16
|
/** Generate a complete HTML document for a single slide */
|
|
3
|
-
export declare const generateSlideHTML: (theme: SlideTheme, slide: SlideLayout, reference?: string) => string;
|
|
17
|
+
export declare const generateSlideHTML: (theme: SlideTheme, slide: SlideLayout, reference?: string, branding?: ResolvedBranding) => string;
|
package/lib/slide/render.js
CHANGED
|
@@ -21,18 +21,56 @@ const buildCdnScripts = (theme, slide) => {
|
|
|
21
21
|
}
|
|
22
22
|
return scripts.join("\n");
|
|
23
23
|
};
|
|
24
|
+
/** Map branding logo position to Tailwind CSS classes */
|
|
25
|
+
const logoPositionClasses = {
|
|
26
|
+
"top-left": "top-5 left-6",
|
|
27
|
+
"top-right": "top-5 right-6",
|
|
28
|
+
"bottom-left": "bottom-5 left-6",
|
|
29
|
+
"bottom-right": "bottom-5 right-6",
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Render branding background layers.
|
|
33
|
+
* - Without bgOpacity: image overlaid on slide bg at given opacity
|
|
34
|
+
* - With bgOpacity: image at full opacity, then slide bg color as semi-transparent overlay
|
|
35
|
+
*/
|
|
36
|
+
const renderBrandingBackground = (branding, bgHex) => {
|
|
37
|
+
if (!branding.backgroundImage)
|
|
38
|
+
return "";
|
|
39
|
+
const { dataUrl, size, opacity, bgOpacity } = branding.backgroundImage;
|
|
40
|
+
const bgSize = size === "fill" ? "100% 100%" : size;
|
|
41
|
+
if (bgOpacity !== undefined) {
|
|
42
|
+
const parts = [];
|
|
43
|
+
parts.push(`<div class="absolute inset-0 z-0" style="background-image:url('${dataUrl}');background-size:${bgSize};background-position:center;background-repeat:no-repeat;opacity:${opacity}"></div>`);
|
|
44
|
+
parts.push(`<div class="absolute inset-0 z-0" style="background-color:#${bgHex};opacity:${bgOpacity}"></div>`);
|
|
45
|
+
return parts.join("\n");
|
|
46
|
+
}
|
|
47
|
+
return `<div class="absolute inset-0 z-0" style="background-image:url('${dataUrl}');background-size:${bgSize};background-position:center;background-repeat:no-repeat;opacity:${opacity}"></div>`;
|
|
48
|
+
};
|
|
49
|
+
/** Render branding logo element */
|
|
50
|
+
const renderBrandingLogo = (branding) => {
|
|
51
|
+
if (!branding.logo)
|
|
52
|
+
return "";
|
|
53
|
+
const { dataUrl, position, width } = branding.logo;
|
|
54
|
+
const posClasses = logoPositionClasses[position] ?? logoPositionClasses["top-right"];
|
|
55
|
+
return `<img class="absolute ${posClasses} z-10" src="${dataUrl}" width="${width}" alt="" style="pointer-events:none">`;
|
|
56
|
+
};
|
|
24
57
|
/** Generate a complete HTML document for a single slide */
|
|
25
|
-
export const generateSlideHTML = (theme, slide, reference) => {
|
|
58
|
+
export const generateSlideHTML = (theme, slide, reference, branding) => {
|
|
26
59
|
const content = renderSlideContent(slide);
|
|
27
60
|
const twConfig = buildTailwindConfig(theme);
|
|
28
61
|
const cdnScripts = buildCdnScripts(theme, slide);
|
|
29
62
|
const slideStyle = slide.style;
|
|
30
|
-
const
|
|
31
|
-
const
|
|
63
|
+
const hasBgOpacity = branding?.backgroundImage?.bgOpacity !== undefined;
|
|
64
|
+
const bgCls = hasBgOpacity || slideStyle?.bgColor ? "" : "bg-d-bg";
|
|
65
|
+
const bgColorStyle = slideStyle?.bgColor ? ` style="background-color:#${sanitizeHex(slideStyle.bgColor)}"` : "";
|
|
66
|
+
const inlineStyle = hasBgOpacity ? "" : bgColorStyle;
|
|
32
67
|
const footer = slideStyle?.footer ? `<p class="absolute bottom-2 right-4 text-xs text-d-dim font-body">${escapeHtml(slideStyle.footer)}</p>` : "";
|
|
33
68
|
const referenceHtml = reference
|
|
34
69
|
? `<div class="mt-auto px-4 pb-2"><p class="text-sm text-d-muted font-body opacity-80">${escapeHtml(reference)}</p></div>`
|
|
35
70
|
: "";
|
|
71
|
+
const bgHex = sanitizeHex(slideStyle?.bgColor ?? theme.colors.bg);
|
|
72
|
+
const brandingBg = branding ? renderBrandingBackground(branding, bgHex) : "";
|
|
73
|
+
const brandingLogo = branding ? renderBrandingLogo(branding) : "";
|
|
36
74
|
return `<!DOCTYPE html>
|
|
37
75
|
<html lang="en" class="h-full">
|
|
38
76
|
<head>
|
|
@@ -47,10 +85,14 @@ ${cdnScripts}
|
|
|
47
85
|
</head>
|
|
48
86
|
<body class="h-full">
|
|
49
87
|
<div class="relative overflow-hidden ${bgCls} w-full h-full flex flex-col"${inlineStyle}>
|
|
88
|
+
${brandingBg}
|
|
89
|
+
<div class="relative z-[1] flex flex-col flex-1">
|
|
50
90
|
${content}
|
|
51
91
|
${referenceHtml}
|
|
52
92
|
${footer}
|
|
53
93
|
</div>
|
|
94
|
+
${brandingLogo}
|
|
95
|
+
</div>
|
|
54
96
|
</body>
|
|
55
97
|
</html>`;
|
|
56
98
|
};
|
package/lib/slide/schema.d.ts
CHANGED
|
@@ -4351,6 +4351,66 @@ export declare const funnelSlideSchema: z.ZodObject<{
|
|
|
4351
4351
|
}, z.core.$strip>>;
|
|
4352
4352
|
layout: z.ZodLiteral<"funnel">;
|
|
4353
4353
|
}, z.core.$strip>;
|
|
4354
|
+
export declare const slideBrandingLogoSchema: z.ZodObject<{
|
|
4355
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4356
|
+
kind: z.ZodLiteral<"url">;
|
|
4357
|
+
url: z.ZodURL;
|
|
4358
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4359
|
+
kind: z.ZodLiteral<"base64">;
|
|
4360
|
+
data: z.ZodString;
|
|
4361
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4362
|
+
kind: z.ZodLiteral<"path">;
|
|
4363
|
+
path: z.ZodString;
|
|
4364
|
+
}, z.core.$strict>], "kind">;
|
|
4365
|
+
position: z.ZodDefault<z.ZodEnum<{
|
|
4366
|
+
"top-left": "top-left";
|
|
4367
|
+
"top-right": "top-right";
|
|
4368
|
+
"bottom-left": "bottom-left";
|
|
4369
|
+
"bottom-right": "bottom-right";
|
|
4370
|
+
}>>;
|
|
4371
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
4372
|
+
}, z.core.$strict>;
|
|
4373
|
+
export declare const slideBrandingSchema: z.ZodObject<{
|
|
4374
|
+
logo: z.ZodOptional<z.ZodObject<{
|
|
4375
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4376
|
+
kind: z.ZodLiteral<"url">;
|
|
4377
|
+
url: z.ZodURL;
|
|
4378
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4379
|
+
kind: z.ZodLiteral<"base64">;
|
|
4380
|
+
data: z.ZodString;
|
|
4381
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4382
|
+
kind: z.ZodLiteral<"path">;
|
|
4383
|
+
path: z.ZodString;
|
|
4384
|
+
}, z.core.$strict>], "kind">;
|
|
4385
|
+
position: z.ZodDefault<z.ZodEnum<{
|
|
4386
|
+
"top-left": "top-left";
|
|
4387
|
+
"top-right": "top-right";
|
|
4388
|
+
"bottom-left": "bottom-left";
|
|
4389
|
+
"bottom-right": "bottom-right";
|
|
4390
|
+
}>>;
|
|
4391
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
4392
|
+
}, z.core.$strict>>;
|
|
4393
|
+
backgroundImage: z.ZodOptional<z.ZodObject<{
|
|
4394
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4395
|
+
kind: z.ZodLiteral<"url">;
|
|
4396
|
+
url: z.ZodURL;
|
|
4397
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4398
|
+
kind: z.ZodLiteral<"base64">;
|
|
4399
|
+
data: z.ZodString;
|
|
4400
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4401
|
+
kind: z.ZodLiteral<"path">;
|
|
4402
|
+
path: z.ZodString;
|
|
4403
|
+
}, z.core.$strict>], "kind">;
|
|
4404
|
+
size: z.ZodOptional<z.ZodEnum<{
|
|
4405
|
+
contain: "contain";
|
|
4406
|
+
cover: "cover";
|
|
4407
|
+
fill: "fill";
|
|
4408
|
+
auto: "auto";
|
|
4409
|
+
}>>;
|
|
4410
|
+
opacity: z.ZodOptional<z.ZodNumber>;
|
|
4411
|
+
bgOpacity: z.ZodOptional<z.ZodNumber>;
|
|
4412
|
+
}, z.core.$strip>>;
|
|
4413
|
+
}, z.core.$strict>;
|
|
4354
4414
|
export declare const slideLayoutSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4355
4415
|
title: z.ZodString;
|
|
4356
4416
|
subtitle: z.ZodOptional<z.ZodString>;
|
|
@@ -8985,6 +9045,47 @@ export declare const mulmoSlideMediaSchema: z.ZodObject<{
|
|
|
8985
9045
|
layout: z.ZodLiteral<"funnel">;
|
|
8986
9046
|
}, z.core.$strip>], "layout">;
|
|
8987
9047
|
reference: z.ZodOptional<z.ZodString>;
|
|
9048
|
+
branding: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
9049
|
+
logo: z.ZodOptional<z.ZodObject<{
|
|
9050
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
9051
|
+
kind: z.ZodLiteral<"url">;
|
|
9052
|
+
url: z.ZodURL;
|
|
9053
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
9054
|
+
kind: z.ZodLiteral<"base64">;
|
|
9055
|
+
data: z.ZodString;
|
|
9056
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
9057
|
+
kind: z.ZodLiteral<"path">;
|
|
9058
|
+
path: z.ZodString;
|
|
9059
|
+
}, z.core.$strict>], "kind">;
|
|
9060
|
+
position: z.ZodDefault<z.ZodEnum<{
|
|
9061
|
+
"top-left": "top-left";
|
|
9062
|
+
"top-right": "top-right";
|
|
9063
|
+
"bottom-left": "bottom-left";
|
|
9064
|
+
"bottom-right": "bottom-right";
|
|
9065
|
+
}>>;
|
|
9066
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
9067
|
+
}, z.core.$strict>>;
|
|
9068
|
+
backgroundImage: z.ZodOptional<z.ZodObject<{
|
|
9069
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
9070
|
+
kind: z.ZodLiteral<"url">;
|
|
9071
|
+
url: z.ZodURL;
|
|
9072
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
9073
|
+
kind: z.ZodLiteral<"base64">;
|
|
9074
|
+
data: z.ZodString;
|
|
9075
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
9076
|
+
kind: z.ZodLiteral<"path">;
|
|
9077
|
+
path: z.ZodString;
|
|
9078
|
+
}, z.core.$strict>], "kind">;
|
|
9079
|
+
size: z.ZodOptional<z.ZodEnum<{
|
|
9080
|
+
contain: "contain";
|
|
9081
|
+
cover: "cover";
|
|
9082
|
+
fill: "fill";
|
|
9083
|
+
auto: "auto";
|
|
9084
|
+
}>>;
|
|
9085
|
+
opacity: z.ZodOptional<z.ZodNumber>;
|
|
9086
|
+
bgOpacity: z.ZodOptional<z.ZodNumber>;
|
|
9087
|
+
}, z.core.$strip>>;
|
|
9088
|
+
}, z.core.$strict>>>;
|
|
8988
9089
|
}, z.core.$strict>;
|
|
8989
9090
|
export type AccentColorKey = z.infer<typeof accentColorKeySchema>;
|
|
8990
9091
|
export type SlideThemeColors = z.infer<typeof slideThemeColorsSchema>;
|
|
@@ -9027,4 +9128,6 @@ export type TableCellValue = z.infer<typeof tableCellValueSchema>;
|
|
|
9027
9128
|
export type TableSlide = z.infer<typeof tableSlideSchema>;
|
|
9028
9129
|
export type FunnelStage = z.infer<typeof funnelStageSchema>;
|
|
9029
9130
|
export type FunnelSlide = z.infer<typeof funnelSlideSchema>;
|
|
9131
|
+
export type SlideBrandingLogo = z.infer<typeof slideBrandingLogoSchema>;
|
|
9132
|
+
export type SlideBranding = z.infer<typeof slideBrandingSchema>;
|
|
9030
9133
|
export type MulmoSlideMedia = z.infer<typeof mulmoSlideMediaSchema>;
|
package/lib/slide/schema.js
CHANGED
|
@@ -358,6 +358,37 @@ export const funnelSlideSchema = z.object({
|
|
|
358
358
|
callout: calloutBarSchema.optional(),
|
|
359
359
|
});
|
|
360
360
|
// ═══════════════════════════════════════════════════════════
|
|
361
|
+
// Branding — logo & background image overlay
|
|
362
|
+
// ═══════════════════════════════════════════════════════════
|
|
363
|
+
/**
|
|
364
|
+
* Media source for branding assets (self-contained definition to avoid
|
|
365
|
+
* circular dependency with src/types/schema.ts).
|
|
366
|
+
*/
|
|
367
|
+
const slideMediaSourceSchema = z.discriminatedUnion("kind", [
|
|
368
|
+
z.object({ kind: z.literal("url"), url: z.url() }).strict(),
|
|
369
|
+
z.object({ kind: z.literal("base64"), data: z.string().min(1) }).strict(),
|
|
370
|
+
z.object({ kind: z.literal("path"), path: z.string().min(1) }).strict(),
|
|
371
|
+
]);
|
|
372
|
+
const slideBackgroundImageSourceSchema = z.object({
|
|
373
|
+
source: slideMediaSourceSchema,
|
|
374
|
+
size: z.enum(["cover", "contain", "fill", "auto"]).optional(),
|
|
375
|
+
opacity: z.number().min(0).max(1).optional(),
|
|
376
|
+
bgOpacity: z.number().min(0).max(1).optional(),
|
|
377
|
+
});
|
|
378
|
+
export const slideBrandingLogoSchema = z
|
|
379
|
+
.object({
|
|
380
|
+
source: slideMediaSourceSchema,
|
|
381
|
+
position: z.enum(["top-left", "top-right", "bottom-left", "bottom-right"]).default("top-right"),
|
|
382
|
+
width: z.number().positive().default(120),
|
|
383
|
+
})
|
|
384
|
+
.strict();
|
|
385
|
+
export const slideBrandingSchema = z
|
|
386
|
+
.object({
|
|
387
|
+
logo: slideBrandingLogoSchema.optional(),
|
|
388
|
+
backgroundImage: slideBackgroundImageSourceSchema.optional(),
|
|
389
|
+
})
|
|
390
|
+
.strict();
|
|
391
|
+
// ═══════════════════════════════════════════════════════════
|
|
361
392
|
// Slide Union & Media Schema
|
|
362
393
|
// ═══════════════════════════════════════════════════════════
|
|
363
394
|
export const slideLayoutSchema = z.discriminatedUnion("layout", [
|
|
@@ -380,5 +411,6 @@ export const mulmoSlideMediaSchema = z
|
|
|
380
411
|
theme: slideThemeSchema.optional(),
|
|
381
412
|
slide: slideLayoutSchema,
|
|
382
413
|
reference: z.string().optional(),
|
|
414
|
+
branding: slideBrandingSchema.nullable().optional(),
|
|
383
415
|
})
|
|
384
416
|
.strict();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ZodSafeParseResult } from "zod";
|
|
2
2
|
import type { MulmoScript } from "../types/type.js";
|
|
3
|
-
type PartialMulmoScript = Record<string, unknown>;
|
|
3
|
+
export type PartialMulmoScript = Record<string, unknown>;
|
|
4
4
|
/**
|
|
5
5
|
* Add $mulmocast version if not present
|
|
6
6
|
*/
|
|
@@ -17,6 +17,7 @@ export type CompleteScriptResult = ZodSafeParseResult<MulmoScript>;
|
|
|
17
17
|
type CompleteScriptOptions = {
|
|
18
18
|
templateName?: string;
|
|
19
19
|
styleName?: string;
|
|
20
|
+
baseDirPath?: string;
|
|
20
21
|
};
|
|
21
22
|
/**
|
|
22
23
|
* Complete a partial MulmoScript with schema defaults, optional style or template
|
|
@@ -4,6 +4,7 @@ import { mulmoScriptSchema } from "../types/schema.js";
|
|
|
4
4
|
import { getScriptFromPromptTemplate } from "../utils/file.js";
|
|
5
5
|
import { currentMulmoScriptVersion } from "../types/const.js";
|
|
6
6
|
import { promptTemplates } from "../data/index.js";
|
|
7
|
+
import { loadMulmoConfig } from "../utils/mulmo_config.js";
|
|
7
8
|
/**
|
|
8
9
|
* Add $mulmocast version if not present
|
|
9
10
|
*/
|
|
@@ -16,7 +17,7 @@ export const addMulmocastVersion = (data) => {
|
|
|
16
17
|
$mulmocast: { version: currentMulmoScriptVersion },
|
|
17
18
|
};
|
|
18
19
|
};
|
|
19
|
-
const deepMergeKeys = ["speechParams", "imageParams", "movieParams", "audioParams"];
|
|
20
|
+
const deepMergeKeys = ["speechParams", "imageParams", "movieParams", "audioParams", "slideParams"];
|
|
20
21
|
/**
|
|
21
22
|
* Merge base with override (override takes precedence)
|
|
22
23
|
*/
|
|
@@ -82,11 +83,13 @@ export const getStyle = (style) => {
|
|
|
82
83
|
* completeScript(data, { styleName: "./my-style.json" })
|
|
83
84
|
*/
|
|
84
85
|
export const completeScript = (data, options = {}) => {
|
|
85
|
-
const { templateName, styleName } = options;
|
|
86
|
+
const { templateName, styleName, baseDirPath } = options;
|
|
86
87
|
// template and style are mutually exclusive
|
|
87
88
|
if (templateName && styleName) {
|
|
88
89
|
throw new Error("Cannot specify both templateName and styleName. They are mutually exclusive.");
|
|
89
90
|
}
|
|
91
|
+
// Load mulmo.config.json
|
|
92
|
+
const configResult = baseDirPath ? loadMulmoConfig(baseDirPath) : null;
|
|
90
93
|
// Get base config from template or style
|
|
91
94
|
const getBase = () => {
|
|
92
95
|
if (templateName) {
|
|
@@ -97,9 +100,12 @@ export const completeScript = (data, options = {}) => {
|
|
|
97
100
|
}
|
|
98
101
|
return undefined;
|
|
99
102
|
};
|
|
100
|
-
const
|
|
101
|
-
// Merge
|
|
102
|
-
const
|
|
103
|
+
const templateOrStyle = getBase();
|
|
104
|
+
// Merge chain: config.defaults < template/style < input data < config.override
|
|
105
|
+
const defaults = configResult?.defaults;
|
|
106
|
+
const withDefaults = defaults && templateOrStyle ? mergeScripts(defaults, templateOrStyle) : (templateOrStyle ?? defaults);
|
|
107
|
+
const withData = withDefaults ? mergeScripts(withDefaults, data) : data;
|
|
108
|
+
const merged = configResult?.override ? mergeScripts(withData, configResult.override) : withData;
|
|
103
109
|
// Add version if not present
|
|
104
110
|
const withVersion = addMulmocastVersion(merged);
|
|
105
111
|
return mulmoScriptSchema.safeParse(withVersion);
|