mulmocast 2.1.28 → 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.
Files changed (38) hide show
  1. package/README.md +77 -0
  2. package/assets/html/tailwind.html +33 -0
  3. package/lib/cli/commands/tool/index.js +11 -1
  4. package/lib/cli/commands/tool/info/builder.d.ts +6 -0
  5. package/lib/cli/commands/tool/info/builder.js +13 -0
  6. package/lib/cli/commands/tool/info/handler.d.ts +6 -0
  7. package/lib/cli/commands/tool/info/handler.js +219 -0
  8. package/lib/cli/commands/tool/info/index.d.ts +4 -0
  9. package/lib/cli/commands/tool/info/index.js +4 -0
  10. package/lib/data/index.d.ts +1 -0
  11. package/lib/data/index.js +1 -0
  12. package/lib/data/markdownStyles.d.ts +14 -0
  13. package/lib/data/markdownStyles.js +998 -0
  14. package/lib/types/schema.d.ts +72 -5
  15. package/lib/types/schema.js +22 -1
  16. package/lib/types/type.d.ts +4 -1
  17. package/lib/utils/context.d.ts +24 -2
  18. package/lib/utils/html_render.js +13 -5
  19. package/lib/utils/image_plugins/chart.js +2 -5
  20. package/lib/utils/image_plugins/markdown.js +60 -12
  21. package/lib/utils/image_plugins/markdown_layout.d.ts +6 -0
  22. package/lib/utils/image_plugins/markdown_layout.js +127 -0
  23. package/lib/utils/image_plugins/mermaid.d.ts +1 -0
  24. package/lib/utils/image_plugins/mermaid.js +16 -13
  25. package/lib/utils/image_plugins/text_slide.js +3 -2
  26. package/lib/utils/image_plugins/utils.d.ts +2 -0
  27. package/lib/utils/image_plugins/utils.js +13 -0
  28. package/package.json +1 -1
  29. package/scripts/test/test_all_markdown_styles.json +809 -0
  30. package/scripts/test/test_markdown_layout.json +152 -0
  31. package/scripts/test/test_markdown_mermaid.json +58 -0
  32. package/scripts/test/test_markdown_styles.json +53 -0
  33. package/scripts/test/test_text_slide_style.json +70 -0
  34. package/scripts/test/test_vertexai.json +4 -1
  35. package/scripts/test/test_vertexai.json~ +21 -0
  36. package/scripts/test/zenn_combined_example.json +39 -0
  37. package/scripts/test/zenn_layout_samples.json +92 -0
  38. package/scripts/test/zenn_markdown_demo.json +79 -0
@@ -179,9 +179,31 @@ 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>]>>]>;
206
+ style: z.ZodOptional<z.ZodString>;
185
207
  }, z.core.$strict>;
186
208
  export declare const mulmoImageMediaSchema: z.ZodObject<{
187
209
  type: z.ZodLiteral<"image">;
@@ -203,6 +225,7 @@ export declare const mulmoTextSlideMediaSchema: z.ZodObject<{
203
225
  subtitle: z.ZodOptional<z.ZodString>;
204
226
  bullets: z.ZodOptional<z.ZodArray<z.ZodString>>;
205
227
  }, z.core.$strip>;
228
+ style: z.ZodOptional<z.ZodString>;
206
229
  }, z.core.$strict>;
207
230
  export declare const captionSplitSchema: z.ZodDefault<z.ZodEnum<{
208
231
  none: "none";
@@ -270,7 +293,17 @@ export declare const mulmoVisionMediaSchema: z.ZodObject<{
270
293
  }, z.core.$strict>;
271
294
  export declare const mulmoImageAssetSchema: z.ZodUnion<readonly [z.ZodObject<{
272
295
  type: z.ZodLiteral<"markdown">;
273
- 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>]>>]>;
306
+ style: z.ZodOptional<z.ZodString>;
274
307
  }, z.core.$strict>, z.ZodObject<{
275
308
  type: z.ZodLiteral<"web">;
276
309
  url: z.ZodURL;
@@ -329,6 +362,7 @@ export declare const mulmoImageAssetSchema: z.ZodUnion<readonly [z.ZodObject<{
329
362
  subtitle: z.ZodOptional<z.ZodString>;
330
363
  bullets: z.ZodOptional<z.ZodArray<z.ZodString>>;
331
364
  }, z.core.$strip>;
365
+ style: z.ZodOptional<z.ZodString>;
332
366
  }, z.core.$strict>, z.ZodObject<{
333
367
  type: z.ZodLiteral<"chart">;
334
368
  title: z.ZodString;
@@ -796,7 +830,17 @@ export declare const mulmoBeatSchema: z.ZodObject<{
796
830
  description: z.ZodOptional<z.ZodString>;
797
831
  image: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
798
832
  type: z.ZodLiteral<"markdown">;
799
- 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>]>>]>;
843
+ style: z.ZodOptional<z.ZodString>;
800
844
  }, z.core.$strict>, z.ZodObject<{
801
845
  type: z.ZodLiteral<"web">;
802
846
  url: z.ZodURL;
@@ -855,6 +899,7 @@ export declare const mulmoBeatSchema: z.ZodObject<{
855
899
  subtitle: z.ZodOptional<z.ZodString>;
856
900
  bullets: z.ZodOptional<z.ZodArray<z.ZodString>>;
857
901
  }, z.core.$strip>;
902
+ style: z.ZodOptional<z.ZodString>;
858
903
  }, z.core.$strict>, z.ZodObject<{
859
904
  type: z.ZodLiteral<"chart">;
860
905
  title: z.ZodString;
@@ -1980,7 +2025,17 @@ export declare const mulmoScriptSchema: z.ZodObject<{
1980
2025
  description: z.ZodOptional<z.ZodString>;
1981
2026
  image: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
1982
2027
  type: z.ZodLiteral<"markdown">;
1983
- 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>]>>]>;
2038
+ style: z.ZodOptional<z.ZodString>;
1984
2039
  }, z.core.$strict>, z.ZodObject<{
1985
2040
  type: z.ZodLiteral<"web">;
1986
2041
  url: z.ZodURL;
@@ -2039,6 +2094,7 @@ export declare const mulmoScriptSchema: z.ZodObject<{
2039
2094
  subtitle: z.ZodOptional<z.ZodString>;
2040
2095
  bullets: z.ZodOptional<z.ZodArray<z.ZodString>>;
2041
2096
  }, z.core.$strip>;
2097
+ style: z.ZodOptional<z.ZodString>;
2042
2098
  }, z.core.$strict>, z.ZodObject<{
2043
2099
  type: z.ZodLiteral<"chart">;
2044
2100
  title: z.ZodString;
@@ -2868,7 +2924,17 @@ export declare const mulmoStudioSchema: z.ZodObject<{
2868
2924
  description: z.ZodOptional<z.ZodString>;
2869
2925
  image: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
2870
2926
  type: z.ZodLiteral<"markdown">;
2871
- 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>]>>]>;
2937
+ style: z.ZodOptional<z.ZodString>;
2872
2938
  }, z.core.$strict>, z.ZodObject<{
2873
2939
  type: z.ZodLiteral<"web">;
2874
2940
  url: z.ZodURL;
@@ -2927,6 +2993,7 @@ export declare const mulmoStudioSchema: z.ZodObject<{
2927
2993
  subtitle: z.ZodOptional<z.ZodString>;
2928
2994
  bullets: z.ZodOptional<z.ZodArray<z.ZodString>>;
2929
2995
  }, z.core.$strip>;
2996
+ style: z.ZodOptional<z.ZodString>;
2930
2997
  }, z.core.$strict>, z.ZodObject<{
2931
2998
  type: z.ZodLiteral<"chart">;
2932
2999
  title: z.ZodString;
@@ -75,10 +75,30 @@ 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]),
101
+ style: z.string().optional(),
82
102
  })
83
103
  .strict();
84
104
  const mulmoWebMediaSchema = z
@@ -119,6 +139,7 @@ export const mulmoTextSlideMediaSchema = z
119
139
  subtitle: z.string().optional(),
120
140
  bullets: z.array(z.string()).optional(),
121
141
  }),
142
+ style: z.string().optional(),
122
143
  })
123
144
  .strict();
124
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,17 @@ 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
+ }));
287
+ style?: string | undefined;
278
288
  } | {
279
289
  type: "web";
280
290
  url: string;
@@ -321,6 +331,7 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
321
331
  subtitle?: string | undefined;
322
332
  bullets?: string[] | undefined;
323
333
  };
334
+ style?: string | undefined;
324
335
  } | {
325
336
  type: "chart";
326
337
  title: string;
@@ -930,7 +941,17 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
930
941
  };
931
942
  } | {
932
943
  type: "markdown";
933
- 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
+ }));
954
+ style?: string | undefined;
934
955
  } | {
935
956
  type: "web";
936
957
  url: string;
@@ -977,6 +998,7 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
977
998
  subtitle?: string | undefined;
978
999
  bullets?: string[] | undefined;
979
1000
  };
1001
+ style?: string | undefined;
980
1002
  } | {
981
1003
  type: "chart";
982
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
- // height:100% ensures background fills viewport; background:white prevents transparent areas
15
- await page.addStyleTag({ content: "html,body{height:100%;margin:0;padding:0;overflow:hidden;background:white}" });
17
+ // height:100% ensures background fills viewport; only reset html, let body styles come from custom CSS
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,24 +1,72 @@
1
- import { renderMarkdownToImage } from "../html_render.js";
2
- import { parrotingImagePath } from "./utils.js";
3
- 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";
4
6
  export const imageType = "markdown";
5
- const processMarkdown = async (params) => {
6
- 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;
7
14
  if (!beat.image || beat.image.type !== imageType)
8
15
  return;
9
- const markdown = dumpMarkdown(params) ?? "";
10
- await renderMarkdownToImage(markdown, textSlideStyle, imagePath, canvasSize.width, canvasSize.height);
11
- 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);
12
23
  };
13
- const dumpMarkdown = (params) => {
24
+ // Generate full HTML for rendering
25
+ const generateHtml = async (params) => {
14
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;
15
51
  if (!beat.image || beat.image.type !== imageType)
16
52
  return;
17
- 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;
18
57
  };
19
58
  const dumpHtml = async (params) => {
20
- const markdown = dumpMarkdown(params);
21
- 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
+ }
22
70
  };
23
71
  export const process = processMarkdown;
24
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 };
@@ -0,0 +1,127 @@
1
+ import { marked } from "marked";
2
+ import { generateMermaidHtml } from "./mermaid.js";
3
+ // Regex to match mermaid code blocks
4
+ const mermaidBlockRegex = /```mermaid\n([\s\S]*?)```/g;
5
+ // Convert string or string array to markdown string
6
+ const toMarkdownString = (content) => {
7
+ if (Array.isArray(content)) {
8
+ return content.join("\n");
9
+ }
10
+ return content;
11
+ };
12
+ // Replace mermaid code blocks with rendered HTML
13
+ const convertMermaidBlocks = (text) => {
14
+ return text.replace(mermaidBlockRegex, (_match, code) => {
15
+ return generateMermaidHtml(code.trim());
16
+ });
17
+ };
18
+ // Parse markdown content to HTML (with mermaid support)
19
+ const parseMarkdown = async (content) => {
20
+ const text = toMarkdownString(content);
21
+ const textWithMermaidHtml = convertMermaidBlocks(text);
22
+ return await marked.parse(textWithMermaidHtml);
23
+ };
24
+ // Generate header HTML
25
+ const generateHeaderHtml = async (data) => {
26
+ const headerHtml = await parseMarkdown(data);
27
+ return `
28
+ <div class="shrink-0 px-8 py-4 border-b border-gray-200 bg-gray-50">
29
+ <div class="prose prose-lg max-w-none">${headerHtml}</div>
30
+ </div>
31
+ `;
32
+ };
33
+ // Generate sidebar HTML
34
+ const generateSidebarHtml = async (data) => {
35
+ const sidebarHtml = await parseMarkdown(data);
36
+ return `
37
+ <div class="shrink-0 w-56 px-4 py-4 border-r border-gray-200 bg-gray-100 overflow-auto">
38
+ <div class="prose prose-sm max-w-none">${sidebarHtml}</div>
39
+ </div>
40
+ `;
41
+ };
42
+ // Generate row-2 layout HTML (two columns)
43
+ const generateRow2Html = async (data) => {
44
+ const [left, right] = data;
45
+ const leftHtml = await parseMarkdown(left);
46
+ const rightHtml = await parseMarkdown(right);
47
+ return `
48
+ <div class="h-full flex gap-6">
49
+ <div class="flex-1 overflow-auto">
50
+ <div class="prose max-w-none">${leftHtml}</div>
51
+ </div>
52
+ <div class="flex-1 overflow-auto">
53
+ <div class="prose max-w-none">${rightHtml}</div>
54
+ </div>
55
+ </div>
56
+ `;
57
+ };
58
+ // Generate 2x2 grid layout HTML
59
+ const generate2x2Html = async (data) => {
60
+ const [tl, tr, bl, br] = data;
61
+ const [tlHtml, trHtml, blHtml, brHtml] = await Promise.all([parseMarkdown(tl), parseMarkdown(tr), parseMarkdown(bl), parseMarkdown(br)]);
62
+ return `
63
+ <div class="h-full grid grid-cols-2 grid-rows-2 gap-4">
64
+ <div class="overflow-auto p-4 bg-gray-50 rounded-lg">
65
+ <div class="prose prose-sm max-w-none">${tlHtml}</div>
66
+ </div>
67
+ <div class="overflow-auto p-4 bg-gray-50 rounded-lg">
68
+ <div class="prose prose-sm max-w-none">${trHtml}</div>
69
+ </div>
70
+ <div class="overflow-auto p-4 bg-gray-50 rounded-lg">
71
+ <div class="prose prose-sm max-w-none">${blHtml}</div>
72
+ </div>
73
+ <div class="overflow-auto p-4 bg-gray-50 rounded-lg">
74
+ <div class="prose prose-sm max-w-none">${brHtml}</div>
75
+ </div>
76
+ </div>
77
+ `;
78
+ };
79
+ // Generate content HTML (single column)
80
+ const generateContentHtml = async (data) => {
81
+ const contentHtml = await parseMarkdown(data);
82
+ return `<div class="prose max-w-none">${contentHtml}</div>`;
83
+ };
84
+ // Generate Tailwind HTML for layout
85
+ export const generateLayoutHtml = async (md) => {
86
+ const parts = ['<div class="w-full h-full flex flex-col overflow-hidden">'];
87
+ if (md.header) {
88
+ parts.push(await generateHeaderHtml(md.header));
89
+ }
90
+ parts.push('<div class="flex-1 flex min-h-0 overflow-hidden">');
91
+ if (md["sidebar-left"]) {
92
+ parts.push(await generateSidebarHtml(md["sidebar-left"]));
93
+ }
94
+ parts.push('<div class="flex-1 p-6 overflow-auto">');
95
+ if ("row-2" in md) {
96
+ parts.push(await generateRow2Html(md["row-2"]));
97
+ }
98
+ else if ("2x2" in md) {
99
+ parts.push(await generate2x2Html(md["2x2"]));
100
+ }
101
+ else if ("content" in md) {
102
+ parts.push(await generateContentHtml(md.content));
103
+ }
104
+ parts.push("</div>", "</div>", "</div>");
105
+ return parts.join("");
106
+ };
107
+ // Convert layout to plain markdown string
108
+ export const layoutToMarkdown = (md) => {
109
+ const parts = [];
110
+ if (md.header) {
111
+ parts.push(toMarkdownString(md.header));
112
+ }
113
+ if (md["sidebar-left"]) {
114
+ parts.push(toMarkdownString(md["sidebar-left"]));
115
+ }
116
+ if ("row-2" in md) {
117
+ parts.push(...md["row-2"].map(toMarkdownString));
118
+ }
119
+ else if ("2x2" in md) {
120
+ parts.push(...md["2x2"].map(toMarkdownString));
121
+ }
122
+ else if ("content" in md) {
123
+ parts.push(toMarkdownString(md.content));
124
+ }
125
+ return parts.join("\n\n");
126
+ };
127
+ export { toMarkdownString, parseMarkdown };
@@ -1,5 +1,6 @@
1
1
  import { ImageProcessorParams } from "../../types/index.js";
2
2
  export declare const imageType = "mermaid";
3
+ export declare const generateMermaidHtml: (code: string, title?: string) => string;
3
4
  export declare const process: (params: ImageProcessorParams) => Promise<string | undefined>;
4
5
  export declare const path: (params: ImageProcessorParams) => string;
5
6
  export declare const markdown: (params: ImageProcessorParams) => string | undefined;
@@ -1,9 +1,22 @@
1
1
  import { MulmoMediaSourceMethods } from "../../methods/index.js";
2
2
  import { getHTMLFile } from "../file.js";
3
3
  import { renderHTMLToImage, interpolate } from "../html_render.js";
4
- import { parrotingImagePath } from "./utils.js";
5
- import nodeProcess from "node:process";
4
+ import { parrotingImagePath, generateUniqueId } from "./utils.js";
6
5
  export const imageType = "mermaid";
6
+ // Generate mermaid HTML from code string (shared utility)
7
+ export const generateMermaidHtml = (code, title) => {
8
+ const diagramId = generateUniqueId("mermaid");
9
+ const titleHtml = title ? `<h3 class="text-xl font-semibold mb-4">${title}</h3>` : "";
10
+ return `
11
+ <div class="mermaid-container mb-6">
12
+ ${titleHtml}
13
+ <div class="flex justify-center">
14
+ <div id="${diagramId}" class="mermaid">
15
+ ${code.trim()}
16
+ </div>
17
+ </div>
18
+ </div>`;
19
+ };
7
20
  const processMermaid = async (params) => {
8
21
  const { beat, imagePath, canvasSize, context, textSlideStyle } = params;
9
22
  if (!beat?.image || beat.image.type !== imageType)
@@ -38,17 +51,7 @@ const dumpHtml = async (params) => {
38
51
  const title = beat.image.title || "Diagram";
39
52
  const appendix = beat.image.appendix?.join("\n") || "";
40
53
  const fullCode = `${diagramCode}\n${appendix}`.trim();
41
- // eslint-disable-next-line sonarjs/pseudo-random
42
- const diagramId = nodeProcess.env.NODE_ENV === "test" ? "id" : `mermaid-${Math.random().toString(36).substr(2, 9)}`;
43
- return `
44
- <div class="mermaid-container mb-6">
45
- <h3 class="text-xl font-semibold mb-4">${title}</h3>
46
- <div class="flex justify-center">
47
- <div id="${diagramId}" class="mermaid">
48
- ${fullCode}
49
- </div>
50
- </div>
51
- </div>`;
54
+ return generateMermaidHtml(fullCode, title);
52
55
  };
53
56
  export const process = processMermaid;
54
57
  export const path = parrotingImagePath;
@@ -1,5 +1,5 @@
1
1
  import { renderMarkdownToImage } from "../html_render.js";
2
- import { parrotingImagePath } from "./utils.js";
2
+ import { parrotingImagePath, resolveStyle } from "./utils.js";
3
3
  import { marked } from "marked";
4
4
  export const imageType = "textSlide";
5
5
  const processTextSlide = async (params) => {
@@ -7,6 +7,7 @@ const processTextSlide = async (params) => {
7
7
  if (!beat.image || beat.image.type !== imageType)
8
8
  return;
9
9
  const slide = beat.image.slide;
10
+ const style = resolveStyle(beat.image.style, textSlideStyle);
10
11
  const markdown = dumpMarkdown(params) ?? "";
11
12
  const topMargin = (() => {
12
13
  if (slide.bullets?.length && slide.bullets.length > 0) {
@@ -15,7 +16,7 @@ const processTextSlide = async (params) => {
15
16
  const marginTop = slide.subtitle ? canvasSize.height * 0.4 : canvasSize.height * 0.45;
16
17
  return `body {margin-top: ${marginTop}px;}`;
17
18
  })();
18
- await renderMarkdownToImage(markdown, textSlideStyle + topMargin, imagePath, canvasSize.width, canvasSize.height);
19
+ await renderMarkdownToImage(markdown, style + topMargin, imagePath, canvasSize.width, canvasSize.height);
19
20
  return imagePath;
20
21
  };
21
22
  const dumpMarkdown = (params) => {
@@ -1,2 +1,4 @@
1
1
  import { ImageProcessorParams } from "../../types/index.js";
2
2
  export declare const parrotingImagePath: (params: ImageProcessorParams) => string;
3
+ export declare const resolveStyle: (styleName: string | undefined, fallbackStyle: string) => string;
4
+ export declare const generateUniqueId: (prefix: string) => string;
@@ -1,3 +1,16 @@
1
+ import { getMarkdownStyle } from "../../data/markdownStyles.js";
2
+ import { randomUUID } from "node:crypto";
3
+ import nodeProcess from "node:process";
1
4
  export const parrotingImagePath = (params) => {
2
5
  return params.imagePath;
3
6
  };
7
+ export const resolveStyle = (styleName, fallbackStyle) => {
8
+ const customStyle = styleName ? getMarkdownStyle(styleName) : undefined;
9
+ return customStyle ? customStyle.css : fallbackStyle;
10
+ };
11
+ export const generateUniqueId = (prefix) => {
12
+ if (nodeProcess.env.NODE_ENV === "test") {
13
+ return "id";
14
+ }
15
+ return `${prefix}-${randomUUID().slice(0, 8)}`;
16
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmocast",
3
- "version": "2.1.28",
3
+ "version": "2.1.30",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "lib/index.node.js",