mulmocast 2.1.29 → 2.1.30

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 CHANGED
@@ -343,6 +343,50 @@ To force regeneration, delete the old files — including temporary files — un
343
343
 
344
344
  If you modify the text or instruction fields in a MulmoScript, mulmo will automatically detect the changes and regenerate the corresponding audio content upon re-run.
345
345
 
346
+ ## Markdown Slide Styles
347
+
348
+ MulmoCast includes 100 pre-designed CSS styles for markdown slides, organized in 10 categories:
349
+
350
+ | Category | Description | Example Styles |
351
+ |----------|-------------|----------------|
352
+ | business | Professional corporate designs | corporate-blue, executive-gray, finance-green |
353
+ | tech | Technology and developer themes | cyber-neon, matrix-green, terminal-dark |
354
+ | creative | Artistic and expressive styles | artistic-splash, watercolor-soft, graffiti |
355
+ | minimalist | Clean and simple designs | clean-white, zen-beige, nordic-light |
356
+ | nature | Nature-inspired themes | forest-green, ocean-blue, sunset-orange |
357
+ | dark | Dark mode and elegant themes | charcoal-elegant, midnight-blue, obsidian |
358
+ | colorful | Vibrant and bold colors | vibrant-pink, candy-colors, aurora |
359
+ | vintage | Retro and classic styles | retro-70s, art-deco, typewriter |
360
+ | japanese | Japanese aesthetic designs | washi-paper, sakura-pink, zen-garden |
361
+ | geometric | Pattern-based designs | hexagon-pattern, isometric, bauhaus |
362
+
363
+ ### Usage
364
+
365
+ Add the `style` property to a markdown beat:
366
+
367
+ ```json
368
+ {
369
+ "beats": [
370
+ {
371
+ "text": "Corporate presentation slide",
372
+ "image": {
373
+ "type": "markdown",
374
+ "markdown": "# Quarterly Report\n\n## Key Highlights\n\n- Revenue up 15%\n- New market expansion\n- Team growth",
375
+ "style": "corporate-blue"
376
+ }
377
+ }
378
+ ]
379
+ }
380
+ ```
381
+
382
+ ### Discover Available Styles
383
+
384
+ Use the `mulmo tool info` command to see all available styles:
385
+
386
+ ```bash
387
+ mulmo tool info styles
388
+ ```
389
+
346
390
  ## MulmoScript Format
347
391
 
348
392
  MulmoScript is a JSON format to define podcast or video scripts:
@@ -569,6 +613,7 @@ Commands:
569
613
  mulmo tool complete <file> Complete partial MulmoScript with defaults
570
614
  mulmo tool prompt Dump prompt from template
571
615
  mulmo tool schema Dump mulmocast schema
616
+ mulmo tool info [category] Show available options (styles, bgm, voices, etc.)
572
617
 
573
618
  Options:
574
619
  --version Show version number [boolean]
@@ -686,6 +731,38 @@ Examples:
686
731
  mulmo tool complete input.json -t children_book
687
732
  ```
688
733
 
734
+ ```
735
+ mulmo tool info [category]
736
+
737
+ Show available options for MulmoScript configuration
738
+
739
+ Positionals:
740
+ category Category to show info for
741
+ [string] [choices: "styles", "bgm", "templates", "voices", "images", "movies", "llm"]
742
+
743
+ Options:
744
+ --version Show version number [boolean]
745
+ -v, --verbose verbose log [boolean] [required] [default: false]
746
+ -h, --help Show help [boolean]
747
+ -F, --format Output format [string] [choices: "text", "json", "yaml"]
748
+
749
+ Examples:
750
+ # Show all available categories
751
+ mulmo tool info
752
+
753
+ # List all 100 markdown styles
754
+ mulmo tool info styles
755
+
756
+ # List available BGM assets
757
+ mulmo tool info bgm
758
+
759
+ # List TTS providers and voices
760
+ mulmo tool info voices
761
+
762
+ # Output as JSON for programmatic use
763
+ mulmo tool info styles --format json
764
+ ```
765
+
689
766
 
690
767
 
691
768
  ## Contributing
@@ -7,6 +7,39 @@
7
7
  <title>${title}</title>
8
8
  <!-- Tailwind CSS CDN -->
9
9
  <script src="https://cdn.tailwindcss.com"></script>
10
+ <!-- Mermaid CDN for embedded diagrams -->
11
+ <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
12
+ <script>
13
+ mermaid.initialize({
14
+ startOnLoad: true,
15
+ theme: 'default',
16
+ flowchart: { useMaxWidth: true }
17
+ });
18
+ document.addEventListener("DOMContentLoaded", () => {
19
+ const mermaidElements = document.querySelectorAll(".mermaid");
20
+ if (mermaidElements.length > 0) {
21
+ mermaid.init(undefined, ".mermaid");
22
+ mermaidElements.forEach(el => el.dataset.ready = "true");
23
+ }
24
+ });
25
+ </script>
26
+ <style>
27
+ /* Mermaid diagram styling */
28
+ .mermaid {
29
+ display: flex;
30
+ justify-content: center;
31
+ align-items: center;
32
+ width: 100%;
33
+ min-height: 100px;
34
+ }
35
+ .mermaid svg {
36
+ max-width: 100%;
37
+ max-height: 100%;
38
+ width: auto;
39
+ height: auto;
40
+ }
41
+ ${custom_style}
42
+ </style>
10
43
  </head>
11
44
  <body class="bg-white text-gray-800 h-full flex flex-col">
12
45
  ${html_body}
@@ -179,9 +179,30 @@ export declare const mediaSourceMermaidSchema: z.ZodDiscriminatedUnion<[z.ZodObj
179
179
  kind: z.ZodLiteral<"path">;
180
180
  path: z.ZodString;
181
181
  }, z.core.$strict>], "kind">;
182
+ export declare const row2Schema: z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
183
+ export declare const grid2x2Schema: z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
184
+ export declare const markdownLayoutSchema: z.ZodIntersection<z.ZodObject<{
185
+ header: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
186
+ "sidebar-left": z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
187
+ }, z.core.$strip>, z.ZodUnion<readonly [z.ZodObject<{
188
+ "row-2": z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
189
+ }, z.core.$strip>, z.ZodObject<{
190
+ "2x2": z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
191
+ }, z.core.$strip>, z.ZodObject<{
192
+ content: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
193
+ }, z.core.$strip>]>>;
182
194
  export declare const mulmoMarkdownMediaSchema: z.ZodObject<{
183
195
  type: z.ZodLiteral<"markdown">;
184
- markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
196
+ markdown: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodIntersection<z.ZodObject<{
197
+ header: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
198
+ "sidebar-left": z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
199
+ }, z.core.$strip>, z.ZodUnion<readonly [z.ZodObject<{
200
+ "row-2": z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
201
+ }, z.core.$strip>, z.ZodObject<{
202
+ "2x2": z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
203
+ }, z.core.$strip>, z.ZodObject<{
204
+ content: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
205
+ }, z.core.$strip>]>>]>;
185
206
  style: z.ZodOptional<z.ZodString>;
186
207
  }, z.core.$strict>;
187
208
  export declare const mulmoImageMediaSchema: z.ZodObject<{
@@ -204,6 +225,7 @@ export declare const mulmoTextSlideMediaSchema: z.ZodObject<{
204
225
  subtitle: z.ZodOptional<z.ZodString>;
205
226
  bullets: z.ZodOptional<z.ZodArray<z.ZodString>>;
206
227
  }, z.core.$strip>;
228
+ style: z.ZodOptional<z.ZodString>;
207
229
  }, z.core.$strict>;
208
230
  export declare const captionSplitSchema: z.ZodDefault<z.ZodEnum<{
209
231
  none: "none";
@@ -271,7 +293,16 @@ export declare const mulmoVisionMediaSchema: z.ZodObject<{
271
293
  }, z.core.$strict>;
272
294
  export declare const mulmoImageAssetSchema: z.ZodUnion<readonly [z.ZodObject<{
273
295
  type: z.ZodLiteral<"markdown">;
274
- markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
296
+ markdown: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodIntersection<z.ZodObject<{
297
+ header: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
298
+ "sidebar-left": z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
299
+ }, z.core.$strip>, z.ZodUnion<readonly [z.ZodObject<{
300
+ "row-2": z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
301
+ }, z.core.$strip>, z.ZodObject<{
302
+ "2x2": z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
303
+ }, z.core.$strip>, z.ZodObject<{
304
+ content: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
305
+ }, z.core.$strip>]>>]>;
275
306
  style: z.ZodOptional<z.ZodString>;
276
307
  }, z.core.$strict>, z.ZodObject<{
277
308
  type: z.ZodLiteral<"web">;
@@ -331,6 +362,7 @@ export declare const mulmoImageAssetSchema: z.ZodUnion<readonly [z.ZodObject<{
331
362
  subtitle: z.ZodOptional<z.ZodString>;
332
363
  bullets: z.ZodOptional<z.ZodArray<z.ZodString>>;
333
364
  }, z.core.$strip>;
365
+ style: z.ZodOptional<z.ZodString>;
334
366
  }, z.core.$strict>, z.ZodObject<{
335
367
  type: z.ZodLiteral<"chart">;
336
368
  title: z.ZodString;
@@ -798,7 +830,16 @@ export declare const mulmoBeatSchema: z.ZodObject<{
798
830
  description: z.ZodOptional<z.ZodString>;
799
831
  image: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
800
832
  type: z.ZodLiteral<"markdown">;
801
- markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
833
+ markdown: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodIntersection<z.ZodObject<{
834
+ header: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
835
+ "sidebar-left": z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
836
+ }, z.core.$strip>, z.ZodUnion<readonly [z.ZodObject<{
837
+ "row-2": z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
838
+ }, z.core.$strip>, z.ZodObject<{
839
+ "2x2": z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
840
+ }, z.core.$strip>, z.ZodObject<{
841
+ content: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
842
+ }, z.core.$strip>]>>]>;
802
843
  style: z.ZodOptional<z.ZodString>;
803
844
  }, z.core.$strict>, z.ZodObject<{
804
845
  type: z.ZodLiteral<"web">;
@@ -858,6 +899,7 @@ export declare const mulmoBeatSchema: z.ZodObject<{
858
899
  subtitle: z.ZodOptional<z.ZodString>;
859
900
  bullets: z.ZodOptional<z.ZodArray<z.ZodString>>;
860
901
  }, z.core.$strip>;
902
+ style: z.ZodOptional<z.ZodString>;
861
903
  }, z.core.$strict>, z.ZodObject<{
862
904
  type: z.ZodLiteral<"chart">;
863
905
  title: z.ZodString;
@@ -1983,7 +2025,16 @@ export declare const mulmoScriptSchema: z.ZodObject<{
1983
2025
  description: z.ZodOptional<z.ZodString>;
1984
2026
  image: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
1985
2027
  type: z.ZodLiteral<"markdown">;
1986
- markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
2028
+ markdown: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodIntersection<z.ZodObject<{
2029
+ header: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
2030
+ "sidebar-left": z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
2031
+ }, z.core.$strip>, z.ZodUnion<readonly [z.ZodObject<{
2032
+ "row-2": z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
2033
+ }, z.core.$strip>, z.ZodObject<{
2034
+ "2x2": z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
2035
+ }, z.core.$strip>, z.ZodObject<{
2036
+ content: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
2037
+ }, z.core.$strip>]>>]>;
1987
2038
  style: z.ZodOptional<z.ZodString>;
1988
2039
  }, z.core.$strict>, z.ZodObject<{
1989
2040
  type: z.ZodLiteral<"web">;
@@ -2043,6 +2094,7 @@ export declare const mulmoScriptSchema: z.ZodObject<{
2043
2094
  subtitle: z.ZodOptional<z.ZodString>;
2044
2095
  bullets: z.ZodOptional<z.ZodArray<z.ZodString>>;
2045
2096
  }, z.core.$strip>;
2097
+ style: z.ZodOptional<z.ZodString>;
2046
2098
  }, z.core.$strict>, z.ZodObject<{
2047
2099
  type: z.ZodLiteral<"chart">;
2048
2100
  title: z.ZodString;
@@ -2872,7 +2924,16 @@ export declare const mulmoStudioSchema: z.ZodObject<{
2872
2924
  description: z.ZodOptional<z.ZodString>;
2873
2925
  image: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
2874
2926
  type: z.ZodLiteral<"markdown">;
2875
- markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
2927
+ markdown: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodIntersection<z.ZodObject<{
2928
+ header: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
2929
+ "sidebar-left": z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
2930
+ }, z.core.$strip>, z.ZodUnion<readonly [z.ZodObject<{
2931
+ "row-2": z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
2932
+ }, z.core.$strip>, z.ZodObject<{
2933
+ "2x2": z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>], null>;
2934
+ }, z.core.$strip>, z.ZodObject<{
2935
+ content: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
2936
+ }, z.core.$strip>]>>]>;
2876
2937
  style: z.ZodOptional<z.ZodString>;
2877
2938
  }, z.core.$strict>, z.ZodObject<{
2878
2939
  type: z.ZodLiteral<"web">;
@@ -2932,6 +2993,7 @@ export declare const mulmoStudioSchema: z.ZodObject<{
2932
2993
  subtitle: z.ZodOptional<z.ZodString>;
2933
2994
  bullets: z.ZodOptional<z.ZodArray<z.ZodString>>;
2934
2995
  }, z.core.$strip>;
2996
+ style: z.ZodOptional<z.ZodString>;
2935
2997
  }, z.core.$strict>, z.ZodObject<{
2936
2998
  type: z.ZodLiteral<"chart">;
2937
2999
  title: z.ZodString;
@@ -75,10 +75,29 @@ export const mediaSourceMermaidSchema = z.discriminatedUnion("kind", [
75
75
  ]);
76
76
  // String is easier for AI, string array is easier for human
77
77
  const stringOrStringArray = z.union([z.string(), z.array(z.string())]);
78
+ export const row2Schema = z.tuple([
79
+ stringOrStringArray, // left
80
+ stringOrStringArray, // right
81
+ ]);
82
+ export const grid2x2Schema = z.tuple([
83
+ stringOrStringArray, // top-left
84
+ stringOrStringArray, // top-right
85
+ stringOrStringArray, // bottom-left
86
+ stringOrStringArray, // bottom-right
87
+ ]);
88
+ // Frame: optional header and sidebar
89
+ const layoutFrameSchema = z.object({
90
+ header: stringOrStringArray.optional(),
91
+ "sidebar-left": stringOrStringArray.optional(),
92
+ });
93
+ // Main: exactly one of row-2, 2x2, or content
94
+ const layoutMainSchema = z.union([z.object({ "row-2": row2Schema }), z.object({ "2x2": grid2x2Schema }), z.object({ content: stringOrStringArray })]);
95
+ // Combine frame + main (loose validation - extra properties not rejected at schema level)
96
+ export const markdownLayoutSchema = layoutFrameSchema.and(layoutMainSchema);
78
97
  export const mulmoMarkdownMediaSchema = z
79
98
  .object({
80
99
  type: z.literal("markdown"),
81
- markdown: stringOrStringArray,
100
+ markdown: z.union([stringOrStringArray, markdownLayoutSchema]),
82
101
  style: z.string().optional(),
83
102
  })
84
103
  .strict();
@@ -120,6 +139,7 @@ export const mulmoTextSlideMediaSchema = z
120
139
  subtitle: z.string().optional(),
121
140
  bullets: z.array(z.string()).optional(),
122
141
  }),
142
+ style: z.string().optional(),
123
143
  })
124
144
  .strict();
125
145
  export const captionSplitSchema = z.enum(["none", "estimate"]).default("none");
@@ -1,5 +1,5 @@
1
1
  import { type CallbackFunction } from "graphai";
2
- import { langSchema, localizedTextSchema, mulmoBeatSchema, mulmoScriptSchema, mulmoStudioSchema, mulmoStudioBeatSchema, mulmoStoryboardSchema, mulmoStoryboardSceneSchema, mulmoStudioMultiLingualSchema, mulmoStudioMultiLingualArraySchema, mulmoStudioMultiLingualDataSchema, mulmoStudioMultiLingualFileSchema, speakerDictionarySchema, speakerSchema, mulmoSpeechParamsSchema, mulmoImageParamsSchema, mulmoImageParamsImagesValueSchema, mulmoImageParamsImagesSchema, mulmoFillOptionSchema, mulmoTransitionSchema, mulmoVideoFilterSchema, mulmoMovieParamsSchema, mulmoSoundEffectParamsSchema, mulmoLipSyncParamsSchema, textSlideParamsSchema, speechOptionsSchema, speakerDataSchema, mulmoCanvasDimensionSchema, mulmoPromptTemplateSchema, mulmoPromptTemplateFileSchema, text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoPresentationStyleSchema, multiLingualTextsSchema, mulmoImageAssetSchema, mulmoMermaidMediaSchema, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mediaSourceMermaidSchema, mulmoSessionStateSchema, mulmoOpenAIImageModelSchema, mulmoGoogleImageModelSchema, mulmoGoogleMovieModelSchema, mulmoReplicateMovieModelSchema, mulmoImagePromptMediaSchema } from "./schema.js";
2
+ import { langSchema, localizedTextSchema, mulmoBeatSchema, mulmoScriptSchema, mulmoStudioSchema, mulmoStudioBeatSchema, mulmoStoryboardSchema, mulmoStoryboardSceneSchema, mulmoStudioMultiLingualSchema, mulmoStudioMultiLingualArraySchema, mulmoStudioMultiLingualDataSchema, mulmoStudioMultiLingualFileSchema, speakerDictionarySchema, speakerSchema, mulmoSpeechParamsSchema, mulmoImageParamsSchema, mulmoImageParamsImagesValueSchema, mulmoImageParamsImagesSchema, mulmoFillOptionSchema, mulmoTransitionSchema, mulmoVideoFilterSchema, mulmoMovieParamsSchema, mulmoSoundEffectParamsSchema, mulmoLipSyncParamsSchema, textSlideParamsSchema, speechOptionsSchema, speakerDataSchema, mulmoCanvasDimensionSchema, mulmoPromptTemplateSchema, mulmoPromptTemplateFileSchema, text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoPresentationStyleSchema, multiLingualTextsSchema, mulmoImageAssetSchema, mulmoMermaidMediaSchema, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mediaSourceMermaidSchema, mulmoSessionStateSchema, mulmoOpenAIImageModelSchema, mulmoGoogleImageModelSchema, mulmoGoogleMovieModelSchema, mulmoReplicateMovieModelSchema, mulmoImagePromptMediaSchema, markdownLayoutSchema, row2Schema, grid2x2Schema } from "./schema.js";
3
3
  import { pdf_modes, pdf_sizes, storyToScriptGenerateMode } from "./const.js";
4
4
  import type { LLM } from "./provider2agent.js";
5
5
  import { z } from "zod";
@@ -46,6 +46,9 @@ export type MulmoGoogleImageModel = z.infer<typeof mulmoGoogleImageModelSchema>;
46
46
  export type MulmoGoogleMovieModel = z.infer<typeof mulmoGoogleMovieModelSchema>;
47
47
  export type MulmoReplicateMovieModel = z.infer<typeof mulmoReplicateMovieModelSchema>;
48
48
  export type MulmoImagePromptMedia = z.infer<typeof mulmoImagePromptMediaSchema>;
49
+ export type MulmoMarkdownLayout = z.infer<typeof markdownLayoutSchema>;
50
+ export type MulmoRow2 = z.infer<typeof row2Schema>;
51
+ export type MulmoGrid2x2 = z.infer<typeof grid2x2Schema>;
49
52
  export type MulmoImageAsset = z.infer<typeof mulmoImageAssetSchema>;
50
53
  export type MulmoTextSlideMedia = z.infer<typeof mulmoTextSlideMediaSchema>;
51
54
  export type MulmoMarkdownMedia = z.infer<typeof mulmoMarkdownMediaSchema>;
@@ -274,7 +274,16 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
274
274
  };
275
275
  } | {
276
276
  type: "markdown";
277
- markdown: string | string[];
277
+ markdown: string | string[] | ({
278
+ header?: string | string[] | undefined;
279
+ "sidebar-left"?: string | string[] | undefined;
280
+ } & ({
281
+ "row-2": [string | string[], string | string[]];
282
+ } | {
283
+ "2x2": [string | string[], string | string[], string | string[], string | string[]];
284
+ } | {
285
+ content: string | string[];
286
+ }));
278
287
  style?: string | undefined;
279
288
  } | {
280
289
  type: "web";
@@ -322,6 +331,7 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
322
331
  subtitle?: string | undefined;
323
332
  bullets?: string[] | undefined;
324
333
  };
334
+ style?: string | undefined;
325
335
  } | {
326
336
  type: "chart";
327
337
  title: string;
@@ -931,7 +941,16 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
931
941
  };
932
942
  } | {
933
943
  type: "markdown";
934
- markdown: string | string[];
944
+ markdown: string | string[] | ({
945
+ header?: string | string[] | undefined;
946
+ "sidebar-left"?: string | string[] | undefined;
947
+ } & ({
948
+ "row-2": [string | string[], string | string[]];
949
+ } | {
950
+ "2x2": [string | string[], string | string[], string | string[], string | string[]];
951
+ } | {
952
+ content: string | string[];
953
+ }));
935
954
  style?: string | undefined;
936
955
  } | {
937
956
  type: "web";
@@ -979,6 +998,7 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
979
998
  subtitle?: string | undefined;
980
999
  bullets?: string[] | undefined;
981
1000
  };
1001
+ style?: string | undefined;
982
1002
  } | {
983
1003
  type: "chart";
984
1004
  title: string;
@@ -8,15 +8,23 @@ export const renderHTMLToImage = async (html, outputPath, width, height, isMerma
8
8
  });
9
9
  const page = await browser.newPage();
10
10
  // Set the page content to the HTML generated from the Markdown
11
- await page.setContent(html);
11
+ // Use networkidle0 only for external images, otherwise use domcontentloaded for faster rendering
12
+ const hasExternalImages = html.includes("<img") && /src=["']https?:\/\//.test(html);
13
+ const waitUntil = hasExternalImages ? "networkidle0" : "domcontentloaded";
14
+ await page.setContent(html, { waitUntil, timeout: 30000 });
12
15
  // Adjust page settings if needed (like width, height, etc.)
13
16
  await page.setViewport({ width, height });
14
17
  // height:100% ensures background fills viewport; only reset html, let body styles come from custom CSS
15
18
  await page.addStyleTag({ content: "html{height:100%;margin:0;padding:0;overflow:hidden}" });
16
19
  if (isMermaid) {
20
+ // Wait for mermaid library to load from CDN
21
+ await page.waitForFunction(() => typeof window.mermaid !== "undefined", { timeout: 20000 });
22
+ // Wait until all mermaid elements have SVG rendered
17
23
  await page.waitForFunction(() => {
18
- const element = document.querySelector(".mermaid");
19
- return element && element.dataset.ready === "true";
24
+ const elements = document.querySelectorAll(".mermaid");
25
+ if (elements.length === 0)
26
+ return true;
27
+ return Array.from(elements).every((el) => el.querySelector("svg") !== null);
20
28
  }, { timeout: 20000 });
21
29
  }
22
30
  // Wait for Chart.js to finish rendering if this is a chart
@@ -1,7 +1,6 @@
1
1
  import { getHTMLFile } from "../file.js";
2
2
  import { renderHTMLToImage, interpolate } from "../html_render.js";
3
- import { parrotingImagePath } from "./utils.js";
4
- import nodeProcess from "node:process";
3
+ import { parrotingImagePath, generateUniqueId } from "./utils.js";
5
4
  export const imageType = "chart";
6
5
  const processChart = async (params) => {
7
6
  const { beat, imagePath, canvasSize, textSlideStyle } = params;
@@ -28,9 +27,7 @@ const dumpHtml = async (params) => {
28
27
  return;
29
28
  const chartData = JSON.stringify(beat.image.chartData, null, 2);
30
29
  const title = beat.image.title || "Chart";
31
- // Safe: UI-only jitter; no security or fairness implications.
32
- // eslint-disable-next-line sonarjs/pseudo-random
33
- const chartId = nodeProcess.env.NODE_ENV === "test" ? "id" : `chart-${Math.random().toString(36).substr(2, 9)}`;
30
+ const chartId = generateUniqueId("chart");
34
31
  return `
35
32
  <div class="chart-container mb-6">
36
33
  <h3 class="text-xl font-semibold mb-4">${title}</h3>
@@ -1,29 +1,72 @@
1
- import { renderMarkdownToImage } from "../html_render.js";
2
- import { parrotingImagePath } from "./utils.js";
3
- import { getMarkdownStyle } from "../../data/markdownStyles.js";
4
- import { marked } from "marked";
1
+ import { getHTMLFile } from "../file.js";
2
+ import { renderHTMLToImage, interpolate } from "../html_render.js";
3
+ import { parrotingImagePath, resolveStyle } from "./utils.js";
4
+ import { generateLayoutHtml, layoutToMarkdown, toMarkdownString, parseMarkdown } from "./markdown_layout.js";
5
+ import { isObject } from "graphai";
5
6
  export const imageType = "markdown";
6
- const processMarkdown = async (params) => {
7
- const { beat, imagePath, textSlideStyle, canvasSize } = params;
7
+ // Type guard for object (data) format
8
+ const isMarkdownLayout = (md) => {
9
+ return isObject(md) && !Array.isArray(md);
10
+ };
11
+ // Generate markdown in order: header → sidebar-left → content
12
+ const dumpMarkdown = (params) => {
13
+ const { beat } = params;
8
14
  if (!beat.image || beat.image.type !== imageType)
9
15
  return;
10
- const markdown = dumpMarkdown(params) ?? "";
11
- // Use custom style if specified, otherwise use default textSlideStyle
12
- const styleName = beat.image.style;
13
- const customStyle = styleName ? getMarkdownStyle(styleName) : undefined;
14
- const style = customStyle ? customStyle.css : textSlideStyle;
15
- await renderMarkdownToImage(markdown, style, imagePath, canvasSize.width, canvasSize.height);
16
- return imagePath;
16
+ const md = beat.image.markdown;
17
+ // text | text[] format
18
+ if (!isMarkdownLayout(md)) {
19
+ return toMarkdownString(md);
20
+ }
21
+ // object (data) format
22
+ return layoutToMarkdown(md);
17
23
  };
18
- const dumpMarkdown = (params) => {
24
+ // Generate full HTML for rendering
25
+ const generateHtml = async (params) => {
19
26
  const { beat } = params;
27
+ if (!beat.image || beat.image.type !== imageType)
28
+ return "";
29
+ const md = beat.image.markdown;
30
+ const style = resolveStyle(beat.image.style, params.textSlideStyle);
31
+ if (isMarkdownLayout(md)) {
32
+ const htmlBody = await generateLayoutHtml(md);
33
+ const template = getHTMLFile("tailwind");
34
+ return interpolate(template, {
35
+ title: "Markdown Layout",
36
+ html_body: htmlBody,
37
+ custom_style: style,
38
+ });
39
+ }
40
+ const markdown = dumpMarkdown(params) ?? "";
41
+ const body = await parseMarkdown(markdown);
42
+ return `<html><head><style>${style}</style></head><body>${body}</body></html>`;
43
+ };
44
+ // Check if markdown content contains mermaid code blocks
45
+ const containsMermaid = (md) => {
46
+ const text = isMarkdownLayout(md) ? layoutToMarkdown(md) : toMarkdownString(md);
47
+ return text.includes("```mermaid");
48
+ };
49
+ const processMarkdown = async (params) => {
50
+ const { beat, imagePath, canvasSize } = params;
20
51
  if (!beat.image || beat.image.type !== imageType)
21
52
  return;
22
- return Array.isArray(beat.image.markdown) ? beat.image.markdown.join("\n") : beat.image.markdown;
53
+ const html = await generateHtml(params);
54
+ const hasMermaid = containsMermaid(beat.image.markdown);
55
+ await renderHTMLToImage(html, imagePath, canvasSize.width, canvasSize.height, hasMermaid);
56
+ return imagePath;
23
57
  };
24
58
  const dumpHtml = async (params) => {
25
- const markdown = dumpMarkdown(params);
26
- return await marked.parse(markdown ?? "");
59
+ const { beat } = params;
60
+ if (!beat.image || beat.image.type !== imageType)
61
+ return "";
62
+ const md = beat.image.markdown;
63
+ if (isMarkdownLayout(md)) {
64
+ return await generateLayoutHtml(md);
65
+ }
66
+ else {
67
+ const markdown = dumpMarkdown(params);
68
+ return await parseMarkdown(markdown ?? "");
69
+ }
27
70
  };
28
71
  export const process = processMarkdown;
29
72
  export const path = parrotingImagePath;
@@ -0,0 +1,6 @@
1
+ import { type MulmoMarkdownLayout } from "../../types/type.js";
2
+ declare const toMarkdownString: (content: string | string[]) => string;
3
+ declare const parseMarkdown: (content: string | string[]) => Promise<string>;
4
+ export declare const generateLayoutHtml: (md: MulmoMarkdownLayout) => Promise<string>;
5
+ export declare const layoutToMarkdown: (md: MulmoMarkdownLayout) => string;
6
+ export { toMarkdownString, parseMarkdown };