mulmocast 2.9.2 → 2.10.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/lib/cli/bin.js +0 -0
- package/lib/index.browser.d.ts +7 -0
- package/lib/index.browser.js +6 -0
- package/lib/types/const.d.ts +7 -0
- package/lib/types/const.js +7 -0
- package/lib/types/schema.js +6 -2
- package/lib/utils/beat_html/chart.d.ts +15 -0
- package/lib/utils/beat_html/chart.js +21 -0
- package/lib/utils/beat_html/html_tailwind.d.ts +19 -0
- package/lib/utils/beat_html/html_tailwind.js +20 -0
- package/lib/utils/beat_html/index.d.ts +33 -0
- package/lib/utils/beat_html/index.js +65 -0
- package/lib/utils/beat_html/markdown.d.ts +20 -0
- package/lib/utils/beat_html/markdown.js +25 -0
- package/lib/utils/beat_html/media.d.ts +14 -0
- package/lib/utils/beat_html/media.js +19 -0
- package/lib/utils/beat_html/mermaid.d.ts +14 -0
- package/lib/utils/beat_html/mermaid.js +22 -0
- package/lib/utils/beat_html/slide.d.ts +21 -0
- package/lib/utils/beat_html/slide.js +33 -0
- package/lib/utils/beat_html/text_slide.d.ts +17 -0
- package/lib/utils/beat_html/text_slide.js +24 -0
- package/lib/utils/beat_html/type.d.ts +73 -0
- package/lib/utils/element_id.d.ts +26 -0
- package/lib/utils/element_id.js +31 -0
- package/lib/utils/html_escape.d.ts +14 -0
- package/lib/utils/html_escape.js +30 -0
- package/lib/utils/image_plugins/bg_image_util.js +2 -1
- package/lib/utils/image_plugins/chart.js +6 -30
- package/lib/utils/image_plugins/chart_html.d.ts +24 -0
- package/lib/utils/image_plugins/chart_html.js +81 -0
- package/lib/utils/image_plugins/html_tailwind.js +2 -5
- package/lib/utils/image_plugins/html_tailwind_markup.d.ts +21 -0
- package/lib/utils/image_plugins/html_tailwind_markup.js +8 -0
- package/lib/utils/image_plugins/markdown.js +41 -30
- package/lib/utils/image_plugins/markdown_layout.d.ts +46 -4
- package/lib/utils/image_plugins/markdown_layout.js +96 -83
- package/lib/utils/image_plugins/media_html.d.ts +16 -0
- package/lib/utils/image_plugins/media_html.js +39 -0
- package/lib/utils/image_plugins/mermaid.d.ts +1 -0
- package/lib/utils/image_plugins/mermaid.js +4 -16
- package/lib/utils/image_plugins/mermaid_html.d.ts +22 -0
- package/lib/utils/image_plugins/mermaid_html.js +36 -0
- package/lib/utils/image_plugins/movie.js +2 -15
- package/lib/utils/sdk_timeout.d.ts +3 -0
- package/lib/utils/sdk_timeout.js +7 -0
- package/lib/utils/swipe_to_html.d.ts +18 -0
- package/lib/utils/swipe_to_html.js +26 -2
- package/package.json +11 -10
- package/scripts/test/test_captions_2.json~ +278 -0
- package/scripts/test/test_en_portrait.json~ +31 -0
- package/scripts/test/test_slide_image_ref_gemini_en.json~ +289 -0
- package/scripts/test/test_vertexai.json~ +21 -0
- package/lib/agents/image_google_agent.d.ts +0 -5
- package/lib/agents/image_google_agent.js +0 -86
- package/lib/agents/movie_google_agent.d.ts +0 -9
- package/lib/agents/movie_google_agent.js +0 -122
- package/lib/agents/mulmo_prompts_agent.d.ts +0 -7
- package/lib/agents/mulmo_prompts_agent.js +0 -37
- package/lib/agents/prompts_data.d.ts +0 -15
- package/lib/agents/prompts_data.js +0 -16
- package/lib/agents/tts_nijivoice_agent.d.ts +0 -5
- package/lib/agents/tts_nijivoice_agent.js +0 -69
- package/lib/agents/validate_mulmo_script_agent.d.ts +0 -17
- package/lib/agents/validate_mulmo_script_agent.js +0 -34
- package/lib/cli/args.d.ts +0 -15
- package/lib/cli/args.js +0 -62
- package/lib/cli/cli.d.ts +0 -17
- package/lib/cli/cli.js +0 -117
- package/lib/cli/run.d.ts +0 -2
- package/lib/cli/run.js +0 -3
- package/lib/cli/tool-args.d.ts +0 -16
- package/lib/cli/tool-args.js +0 -64
- package/lib/cli/tool-cli.d.ts +0 -2
- package/lib/cli/tool-cli.js +0 -32
- package/lib/cli/tool_commands/prompt/builder.d.ts +0 -4
- package/lib/cli/tool_commands/prompt/builder.js +0 -11
- package/lib/cli/tool_commands/prompt/handler.d.ts +0 -4
- package/lib/cli/tool_commands/prompt/handler.js +0 -14
- package/lib/cli/tool_commands/prompt/index.d.ts +0 -4
- package/lib/cli/tool_commands/prompt/index.js +0 -4
- package/lib/cli/tool_commands/schema/builder.d.ts +0 -2
- package/lib/cli/tool_commands/schema/builder.js +0 -3
- package/lib/cli/tool_commands/schema/handler.d.ts +0 -2
- package/lib/cli/tool_commands/schema/handler.js +0 -12
- package/lib/cli/tool_commands/schema/index.d.ts +0 -4
- package/lib/cli/tool_commands/schema/index.js +0 -4
- package/lib/cli/tool_commands/scripting/builder.d.ts +0 -20
- package/lib/cli/tool_commands/scripting/builder.js +0 -63
- package/lib/cli/tool_commands/scripting/handler.d.ts +0 -12
- package/lib/cli/tool_commands/scripting/handler.js +0 -36
- package/lib/cli/tool_commands/scripting/index.d.ts +0 -4
- package/lib/cli/tool_commands/scripting/index.js +0 -4
- package/lib/index.d.ts +0 -8
- package/lib/index.js +0 -8
- package/lib/methods/mulmo_script_template.d.ts +0 -4
- package/lib/methods/mulmo_script_template.js +0 -6
- package/lib/methods/mulmo_studio.d.ts +0 -8
- package/lib/methods/mulmo_studio.js +0 -25
- package/lib/slide/blocks.d.ts +0 -13
- package/lib/slide/blocks.js +0 -251
- package/lib/slide/index.d.ts +0 -6
- package/lib/slide/index.js +0 -7
- package/lib/slide/layouts/big_quote.d.ts +0 -2
- package/lib/slide/layouts/big_quote.js +0 -19
- package/lib/slide/layouts/columns.d.ts +0 -2
- package/lib/slide/layouts/columns.js +0 -53
- package/lib/slide/layouts/comparison.d.ts +0 -2
- package/lib/slide/layouts/comparison.js +0 -25
- package/lib/slide/layouts/funnel.d.ts +0 -2
- package/lib/slide/layouts/funnel.js +0 -25
- package/lib/slide/layouts/grid.d.ts +0 -2
- package/lib/slide/layouts/grid.js +0 -38
- package/lib/slide/layouts/index.d.ts +0 -3
- package/lib/slide/layouts/index.js +0 -46
- package/lib/slide/layouts/matrix.d.ts +0 -2
- package/lib/slide/layouts/matrix.js +0 -53
- package/lib/slide/layouts/split.d.ts +0 -2
- package/lib/slide/layouts/split.js +0 -51
- package/lib/slide/layouts/stats.d.ts +0 -2
- package/lib/slide/layouts/stats.js +0 -23
- package/lib/slide/layouts/table.d.ts +0 -2
- package/lib/slide/layouts/table.js +0 -10
- package/lib/slide/layouts/timeline.d.ts +0 -2
- package/lib/slide/layouts/timeline.js +0 -27
- package/lib/slide/layouts/title.d.ts +0 -2
- package/lib/slide/layouts/title.js +0 -19
- package/lib/slide/layouts/waterfall.d.ts +0 -2
- package/lib/slide/layouts/waterfall.js +0 -63
- package/lib/slide/render.d.ts +0 -17
- package/lib/slide/render.js +0 -101
- package/lib/slide/schema.d.ts +0 -9309
- package/lib/slide/schema.js +0 -434
- package/lib/slide/utils.d.ts +0 -76
- package/lib/slide/utils.js +0 -243
- package/lib/tools/prompt.js +0 -18
- package/lib/tools/seed.d.ts +0 -3
- package/lib/tools/seed.js +0 -211
- package/lib/tools/seed_from_url.d.ts +0 -3
- package/lib/tools/seed_from_url.js +0 -188
- package/lib/types/slide.d.ts +0 -9309
- package/lib/types/slide.js +0 -434
- package/lib/utils/assets.d.ts +0 -18
- package/lib/utils/assets.js +0 -101
- package/lib/utils/cli.d.ts +0 -1
- package/lib/utils/cli.js +0 -15
- package/lib/utils/const.d.ts +0 -12
- package/lib/utils/const.js +0 -12
- package/lib/utils/image_preprocess.d.ts +0 -14
- package/lib/utils/image_preprocess.js +0 -52
- package/lib/utils/markdown.d.ts +0 -3
- package/lib/utils/markdown.js +0 -42
- package/lib/utils/pdf.d.ts +0 -9
- package/lib/utils/pdf.js +0 -77
- package/lib/utils/preprocess.d.ts +0 -343
- package/lib/utils/preprocess.js +0 -58
- package/lib/utils/provider2agent.d.ts +0 -141
- package/lib/utils/provider2agent.js +0 -267
- package/lib/utils/text_hash.d.ts +0 -1
- package/lib/utils/text_hash.js +0 -4
- /package/lib/{tools/prompt.d.ts → utils/beat_html/type.js} +0 -0
|
@@ -1,118 +1,132 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import { Marked } from "marked";
|
|
2
|
+
import { mermaidHtml } from "./mermaid_html.js";
|
|
3
|
+
/**
|
|
4
|
+
* The one markdown implementation, shared by the Node render path
|
|
5
|
+
* (`image_plugins/markdown.ts`, which turns it into a PNG) and the browser fragment path
|
|
6
|
+
* (`beat_html/markdown.ts`, which hands it to a host's DOM).
|
|
7
|
+
*
|
|
8
|
+
* Deliberately pure: no `node:*`, no filesystem, no puppeteer. That is what lets the
|
|
9
|
+
* browser side import it rather than keep a second copy — and a second copy is not
|
|
10
|
+
* hypothetical, it existed for one commit and the two immediately disagreed about which
|
|
11
|
+
* fences are diagrams. `test/beat_html/test_browser_safety.ts` bundles the browser entry
|
|
12
|
+
* with esbuild and fails if anything here reaches Node.
|
|
13
|
+
*
|
|
14
|
+
* Element ids come from a caller-supplied generator, because that is the only thing the
|
|
15
|
+
* two callers genuinely need to differ on.
|
|
16
|
+
*/
|
|
17
|
+
/** A markdown field is written either as one string or as lines to be joined. */
|
|
18
|
+
export const toMarkdownString = (content) => (Array.isArray(content) ? content.join("\n") : content);
|
|
19
|
+
/**
|
|
20
|
+
* A fence's info string can carry more than the language (```mermaid title="x"), and
|
|
21
|
+
* marked hands it over verbatim. The language is its first word.
|
|
22
|
+
*/
|
|
23
|
+
const languageOf = (info) => (info ?? "").trim().split(/\s+/)[0].toLowerCase();
|
|
24
|
+
/**
|
|
25
|
+
* Renders markdown, turning fenced `mermaid` blocks into mermaid markup.
|
|
26
|
+
*
|
|
27
|
+
* Asked of marked's own lexer rather than matched against the raw source. A regex over
|
|
28
|
+
* text was what both copies used, and review found four inputs it got wrong: a CRLF fence
|
|
29
|
+
* and a fence with trailing whitespace after the language were missed, while a mermaid
|
|
30
|
+
* fence quoted INSIDE an outer ````markdown fence, and one inside an indented code block,
|
|
31
|
+
* were converted — turning somebody's example of mermaid syntax into an actual diagram.
|
|
32
|
+
* marked decides what is a code block and what language it is, so all four follow from
|
|
33
|
+
* asking it.
|
|
34
|
+
*
|
|
35
|
+
* A fresh `Marked` instance per call rather than the global `marked.use`, because the
|
|
36
|
+
* renderer closes over this call's mermaid counter — a shared one would leak ids between
|
|
37
|
+
* beats.
|
|
38
|
+
*/
|
|
39
|
+
export const renderMarkdownContent = (content, nextId) => {
|
|
40
|
+
let hasMermaid = false;
|
|
41
|
+
const md = new Marked({
|
|
42
|
+
async: false,
|
|
43
|
+
renderer: {
|
|
44
|
+
code({ text, lang }) {
|
|
45
|
+
if (languageOf(lang) !== "mermaid")
|
|
46
|
+
return false;
|
|
47
|
+
hasMermaid = true;
|
|
48
|
+
return mermaidHtml(text, nextId());
|
|
49
|
+
},
|
|
50
|
+
},
|
|
16
51
|
});
|
|
52
|
+
return { html: md.parse(toMarkdownString(content), { async: false }), hasMermaid };
|
|
17
53
|
};
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return `
|
|
54
|
+
/** The object form of `markdown`, as opposed to a plain string or array of lines. */
|
|
55
|
+
export const isMarkdownLayout = (md) => typeof md === "object" && md !== null && !Array.isArray(md);
|
|
56
|
+
/**
|
|
57
|
+
* The slot markup is main's, verbatim including its indentation. Reformatting it would
|
|
58
|
+
* change every rendered markdown beat's HTML for no reason — semantically inert, but this
|
|
59
|
+
* feeds Puppeteer and the `mulmo html` export, and "no behaviour change" should mean the
|
|
60
|
+
* bytes too.
|
|
61
|
+
*/
|
|
62
|
+
const HEADER_HTML = (inner) => `
|
|
28
63
|
<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">${
|
|
64
|
+
<div class="prose prose-lg max-w-none">${inner}</div>
|
|
30
65
|
</div>
|
|
31
66
|
`;
|
|
32
|
-
|
|
33
|
-
// Generate sidebar HTML
|
|
34
|
-
const generateSidebarHtml = async (data) => {
|
|
35
|
-
const sidebarHtml = await parseMarkdown(data);
|
|
36
|
-
return `
|
|
67
|
+
const SIDEBAR_HTML = (inner) => `
|
|
37
68
|
<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">${
|
|
69
|
+
<div class="prose prose-sm max-w-none">${inner}</div>
|
|
39
70
|
</div>
|
|
40
71
|
`;
|
|
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 `
|
|
72
|
+
const ROW2_HTML = (left, right) => `
|
|
48
73
|
<div class="h-full flex gap-6">
|
|
49
74
|
<div class="flex-1 overflow-auto">
|
|
50
|
-
<div class="prose max-w-none">${
|
|
75
|
+
<div class="prose max-w-none">${left}</div>
|
|
51
76
|
</div>
|
|
52
77
|
<div class="flex-1 overflow-auto">
|
|
53
|
-
<div class="prose max-w-none">${
|
|
78
|
+
<div class="prose max-w-none">${right}</div>
|
|
54
79
|
</div>
|
|
55
80
|
</div>
|
|
56
81
|
`;
|
|
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 `
|
|
82
|
+
const GRID_2X2_HTML = (cells) => `
|
|
63
83
|
<div class="h-full grid grid-cols-2 grid-rows-2 gap-4">
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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>
|
|
84
|
+
${cells
|
|
85
|
+
.map((cell) => ` <div class="overflow-auto p-4 bg-gray-50 rounded-lg">
|
|
86
|
+
<div class="prose prose-sm max-w-none">${cell}</div>
|
|
87
|
+
</div>`)
|
|
88
|
+
.join("\n")}
|
|
76
89
|
</div>
|
|
77
90
|
`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
const CONTENT_HTML = (inner) => `<div class="prose max-w-none">${inner}</div>`;
|
|
92
|
+
/** Lay a markdown layout out in Tailwind: header / sidebar-left / row-2 | 2x2 | content. */
|
|
93
|
+
export const renderMarkdownLayout = (md, nextId) => {
|
|
94
|
+
let hasMermaid = false;
|
|
95
|
+
const render = (content) => {
|
|
96
|
+
const rendered = renderMarkdownContent(content, nextId);
|
|
97
|
+
hasMermaid = hasMermaid || rendered.hasMermaid;
|
|
98
|
+
return rendered.html;
|
|
99
|
+
};
|
|
86
100
|
const parts = ['<div class="w-full h-full flex flex-col overflow-hidden">'];
|
|
87
|
-
if (md.header)
|
|
88
|
-
parts.push(
|
|
89
|
-
}
|
|
101
|
+
if (md.header)
|
|
102
|
+
parts.push(HEADER_HTML(render(md.header)));
|
|
90
103
|
parts.push('<div class="flex-1 flex min-h-0 overflow-hidden">');
|
|
91
|
-
if (md["sidebar-left"])
|
|
92
|
-
parts.push(
|
|
93
|
-
}
|
|
104
|
+
if (md["sidebar-left"])
|
|
105
|
+
parts.push(SIDEBAR_HTML(render(md["sidebar-left"])));
|
|
94
106
|
parts.push('<div class="flex-1 p-6 overflow-auto">');
|
|
95
107
|
if ("row-2" in md) {
|
|
96
|
-
|
|
108
|
+
const [left, right] = md["row-2"];
|
|
109
|
+
parts.push(ROW2_HTML(render(left), render(right)));
|
|
97
110
|
}
|
|
98
111
|
else if ("2x2" in md) {
|
|
99
|
-
parts.push(
|
|
112
|
+
parts.push(GRID_2X2_HTML(md["2x2"].map(render)));
|
|
100
113
|
}
|
|
101
114
|
else if ("content" in md) {
|
|
102
|
-
parts.push(
|
|
115
|
+
parts.push(CONTENT_HTML(render(md.content)));
|
|
103
116
|
}
|
|
104
117
|
parts.push("</div>", "</div>", "</div>");
|
|
105
|
-
return parts.join("");
|
|
118
|
+
return { html: parts.join(""), hasMermaid };
|
|
106
119
|
};
|
|
107
|
-
|
|
120
|
+
/**
|
|
121
|
+
* A layout flattened back to plain markdown, in slot order. Used by `mulmo markdown`,
|
|
122
|
+
* which wants what the beat SAYS rather than how it is laid out.
|
|
123
|
+
*/
|
|
108
124
|
export const layoutToMarkdown = (md) => {
|
|
109
125
|
const parts = [];
|
|
110
|
-
if (md.header)
|
|
126
|
+
if (md.header)
|
|
111
127
|
parts.push(toMarkdownString(md.header));
|
|
112
|
-
|
|
113
|
-
if (md["sidebar-left"]) {
|
|
128
|
+
if (md["sidebar-left"])
|
|
114
129
|
parts.push(toMarkdownString(md["sidebar-left"]));
|
|
115
|
-
}
|
|
116
130
|
if ("row-2" in md) {
|
|
117
131
|
parts.push(...md["row-2"].map(toMarkdownString));
|
|
118
132
|
}
|
|
@@ -124,4 +138,3 @@ export const layoutToMarkdown = (md) => {
|
|
|
124
138
|
}
|
|
125
139
|
return parts.join("\n\n");
|
|
126
140
|
};
|
|
127
|
-
export { toMarkdownString, parseMarkdown };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MulmoMediaSource } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Markup for image and movie media. Pure — no Node, no filesystem — because the Node dump
|
|
4
|
+
* path and the browser fragment path show the same media and must show it the same way.
|
|
5
|
+
*
|
|
6
|
+
* `src` is escaped: `z.url()` accepts a quote, so a url from a script can otherwise close
|
|
7
|
+
* the attribute. Measured — `https://e.com/a.mp4" onerror="pwn()` parses clean.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The src a browser can load from a media source. A `base64` source has no media type in
|
|
11
|
+
* the schema, so there is nothing to build a `data:` URI from; a `path` is emitted as
|
|
12
|
+
* written and left for the host to resolve against its own page.
|
|
13
|
+
*/
|
|
14
|
+
export declare const mediaSrc: (source: MulmoMediaSource) => string | undefined;
|
|
15
|
+
export declare const imageHtml: (src: string, alt: string) => string;
|
|
16
|
+
export declare const movieHtml: (src: string) => string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { escapeHtml } from "@mulmocast/deck/lib/utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Markup for image and movie media. Pure — no Node, no filesystem — because the Node dump
|
|
4
|
+
* path and the browser fragment path show the same media and must show it the same way.
|
|
5
|
+
*
|
|
6
|
+
* `src` is escaped: `z.url()` accepts a quote, so a url from a script can otherwise close
|
|
7
|
+
* the attribute. Measured — `https://e.com/a.mp4" onerror="pwn()` parses clean.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The src a browser can load from a media source. A `base64` source has no media type in
|
|
11
|
+
* the schema, so there is nothing to build a `data:` URI from; a `path` is emitted as
|
|
12
|
+
* written and left for the host to resolve against its own page.
|
|
13
|
+
*/
|
|
14
|
+
export const mediaSrc = (source) => {
|
|
15
|
+
if (source.kind === "url")
|
|
16
|
+
return source.url;
|
|
17
|
+
if (source.kind === "path")
|
|
18
|
+
return source.path;
|
|
19
|
+
return undefined;
|
|
20
|
+
};
|
|
21
|
+
export const imageHtml = (src, alt) => `\n<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" class="w-full rounded-lg shadow-lg" />`;
|
|
22
|
+
export const movieHtml = (src) => {
|
|
23
|
+
const url = escapeHtml(src);
|
|
24
|
+
return `
|
|
25
|
+
<div class="movie-container mb-6">
|
|
26
|
+
<div class="relative w-full" style="padding-bottom: 56.25%; /* 16:9 aspect ratio */">
|
|
27
|
+
<video
|
|
28
|
+
class="absolute top-0 left-0 w-full h-full rounded-lg shadow-lg"
|
|
29
|
+
controls
|
|
30
|
+
preload="metadata"
|
|
31
|
+
>
|
|
32
|
+
<source src="${url}" type="video/mp4">
|
|
33
|
+
<source src="${url}" type="video/webm">
|
|
34
|
+
<source src="${url}" type="video/ogg">
|
|
35
|
+
Your browser does not support the video tag.
|
|
36
|
+
</video>
|
|
37
|
+
</div>
|
|
38
|
+
</div>`;
|
|
39
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ImageProcessorParams } from "../../types/index.js";
|
|
2
2
|
export declare const imageType = "mermaid";
|
|
3
|
+
/** Mermaid markup for this path. The markup itself is shared with the browser path. */
|
|
3
4
|
export declare const generateMermaidHtml: (code: string, title?: string) => string;
|
|
4
5
|
export declare const process: (params: ImageProcessorParams) => Promise<string | undefined>;
|
|
5
6
|
export declare const path: (params: ImageProcessorParams) => string;
|
|
@@ -2,23 +2,12 @@ import { MulmoMediaSourceMethods } from "../../methods/index.js";
|
|
|
2
2
|
import { getHTMLFile } from "../file.js";
|
|
3
3
|
import { renderHTMLToImage, interpolate } from "../html_render.js";
|
|
4
4
|
import { parrotingImagePath, generateUniqueId } from "./utils.js";
|
|
5
|
+
import { escapedMermaidTemplateValues, mermaidHtml } from "./mermaid_html.js";
|
|
5
6
|
import { resolveCombinedStyle } from "./bg_image_util.js";
|
|
6
7
|
import { resolveImageRefs, resolveMovieRefs, resolveRelativeImagePaths } from "./html_tailwind.js";
|
|
7
8
|
export const imageType = "mermaid";
|
|
8
|
-
|
|
9
|
-
export const generateMermaidHtml = (code, title) =>
|
|
10
|
-
const diagramId = generateUniqueId("mermaid");
|
|
11
|
-
const titleHtml = title ? `<h3 class="text-xl font-semibold mb-4">${title}</h3>` : "";
|
|
12
|
-
return `
|
|
13
|
-
<div class="mermaid-container mb-6">
|
|
14
|
-
${titleHtml}
|
|
15
|
-
<div class="flex justify-center">
|
|
16
|
-
<div id="${diagramId}" class="mermaid">
|
|
17
|
-
${code.trim()}
|
|
18
|
-
</div>
|
|
19
|
-
</div>
|
|
20
|
-
</div>`;
|
|
21
|
-
};
|
|
9
|
+
/** Mermaid markup for this path. The markup itself is shared with the browser path. */
|
|
10
|
+
export const generateMermaidHtml = (code, title) => mermaidHtml(code, generateUniqueId("mermaid"), title);
|
|
22
11
|
const processMermaid = async (params) => {
|
|
23
12
|
const { beat, imagePath, canvasSize, context } = params;
|
|
24
13
|
if (!beat?.image || beat.image.type !== imageType)
|
|
@@ -28,9 +17,8 @@ const processMermaid = async (params) => {
|
|
|
28
17
|
if (diagram_code) {
|
|
29
18
|
const combinedStyle = await resolveCombinedStyle(params, beat.image.backgroundImage, beat.image.style);
|
|
30
19
|
const rawHtml = interpolate(template, {
|
|
31
|
-
title
|
|
20
|
+
...escapedMermaidTemplateValues(beat.image.title, `${diagram_code}\n${beat.image.appendix?.join("\n") ?? ""}`),
|
|
32
21
|
style: combinedStyle,
|
|
33
|
-
diagram_code: `${diagram_code}\n${beat.image.appendix?.join("\n") ?? ""}`,
|
|
34
22
|
});
|
|
35
23
|
const resolvedImageRefs = resolveImageRefs(rawHtml, params.imageRefs ?? {});
|
|
36
24
|
const resolvedAllRefs = resolveMovieRefs(resolvedImageRefs, params.movieRefs ?? {});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mermaid markup. Pure — no Node, no filesystem — because both the Node render path and
|
|
3
|
+
* the browser fragment path draw the same diagram and must draw it the same way.
|
|
4
|
+
*
|
|
5
|
+
* The element id is a parameter rather than generated here: the Node path renders once to
|
|
6
|
+
* a PNG so a random id is fine, while the browser path re-renders and needs the same beat
|
|
7
|
+
* to produce the same markup, or a host diffing fragments sees every diagram change
|
|
8
|
+
* identity on every render. That parameter is caller-supplied — the markdown path builds it
|
|
9
|
+
* from an idPrefix, and a beat's `id` comes from the script — so it is validated rather than
|
|
10
|
+
* escaped: see element_id.ts for why the permitted-set rule beats a per-context escaper.
|
|
11
|
+
*
|
|
12
|
+
* Both values are escaped. Mermaid reads the element's innerHTML and entity-decodes it
|
|
13
|
+
* before parsing (mermaid 11.17.0: `o = u.innerHTML; o = entityDecode(o)`), so it receives
|
|
14
|
+
* the original characters either way — measured across ten diagram shapes including `<br/>`
|
|
15
|
+
* in labels, `#quot;`, raw tags and an init directive, the rendered SVG is byte-identical.
|
|
16
|
+
*/
|
|
17
|
+
export declare const mermaidHtml: (code: string, id: string, title?: string) => string;
|
|
18
|
+
/** The two values assets/html/mermaid.html interpolates from user data, escaped for its `<h1>` and `.mermaid` text contexts. */
|
|
19
|
+
export declare const escapedMermaidTemplateValues: (title: string, diagramCode: string) => {
|
|
20
|
+
title: string;
|
|
21
|
+
diagram_code: string;
|
|
22
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { escapeHtml } from "@mulmocast/deck/lib/utils.js";
|
|
2
|
+
import { assertSafeElementId } from "../element_id.js";
|
|
3
|
+
/**
|
|
4
|
+
* Mermaid markup. Pure — no Node, no filesystem — because both the Node render path and
|
|
5
|
+
* the browser fragment path draw the same diagram and must draw it the same way.
|
|
6
|
+
*
|
|
7
|
+
* The element id is a parameter rather than generated here: the Node path renders once to
|
|
8
|
+
* a PNG so a random id is fine, while the browser path re-renders and needs the same beat
|
|
9
|
+
* to produce the same markup, or a host diffing fragments sees every diagram change
|
|
10
|
+
* identity on every render. That parameter is caller-supplied — the markdown path builds it
|
|
11
|
+
* from an idPrefix, and a beat's `id` comes from the script — so it is validated rather than
|
|
12
|
+
* escaped: see element_id.ts for why the permitted-set rule beats a per-context escaper.
|
|
13
|
+
*
|
|
14
|
+
* Both values are escaped. Mermaid reads the element's innerHTML and entity-decodes it
|
|
15
|
+
* before parsing (mermaid 11.17.0: `o = u.innerHTML; o = entityDecode(o)`), so it receives
|
|
16
|
+
* the original characters either way — measured across ten diagram shapes including `<br/>`
|
|
17
|
+
* in labels, `#quot;`, raw tags and an init directive, the rendered SVG is byte-identical.
|
|
18
|
+
*/
|
|
19
|
+
export const mermaidHtml = (code, id, title) => {
|
|
20
|
+
assertSafeElementId(id);
|
|
21
|
+
const titleHtml = title ? `<h3 class="text-xl font-semibold mb-4">${escapeHtml(title)}</h3>` : "";
|
|
22
|
+
return `
|
|
23
|
+
<div class="mermaid-container mb-6">
|
|
24
|
+
${titleHtml}
|
|
25
|
+
<div class="flex justify-center">
|
|
26
|
+
<div id="${id}" class="mermaid">
|
|
27
|
+
${escapeHtml(code.trim())}
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>`;
|
|
31
|
+
};
|
|
32
|
+
/** The two values assets/html/mermaid.html interpolates from user data, escaped for its `<h1>` and `.mermaid` text contexts. */
|
|
33
|
+
export const escapedMermaidTemplateValues = (title, diagramCode) => ({
|
|
34
|
+
title: escapeHtml(title),
|
|
35
|
+
diagram_code: escapeHtml(diagramCode),
|
|
36
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { processSource, pathSource } from "./source.js";
|
|
2
2
|
import { MulmoMediaSourceMethods } from "../../methods/mulmo_media_source.js";
|
|
3
|
+
import { movieHtml } from "./media_html.js";
|
|
3
4
|
export const imageType = "movie";
|
|
4
5
|
const dumpHtml = async (params) => {
|
|
5
6
|
const { beat, context } = params;
|
|
@@ -8,21 +9,7 @@ const dumpHtml = async (params) => {
|
|
|
8
9
|
const moviePathOrUrl = MulmoMediaSourceMethods.resolve(beat.image.source, context);
|
|
9
10
|
if (!moviePathOrUrl)
|
|
10
11
|
return;
|
|
11
|
-
return
|
|
12
|
-
<div class="movie-container mb-6">
|
|
13
|
-
<div class="relative w-full" style="padding-bottom: 56.25%; /* 16:9 aspect ratio */">
|
|
14
|
-
<video
|
|
15
|
-
class="absolute top-0 left-0 w-full h-full rounded-lg shadow-lg"
|
|
16
|
-
controls
|
|
17
|
-
preload="metadata"
|
|
18
|
-
>
|
|
19
|
-
<source src="${moviePathOrUrl}" type="video/mp4">
|
|
20
|
-
<source src="${moviePathOrUrl}" type="video/webm">
|
|
21
|
-
<source src="${moviePathOrUrl}" type="video/ogg">
|
|
22
|
-
Your browser does not support the video tag.
|
|
23
|
-
</video>
|
|
24
|
-
</div>
|
|
25
|
-
</div>`;
|
|
12
|
+
return movieHtml(moviePathOrUrl);
|
|
26
13
|
};
|
|
27
14
|
export const process = processSource(imageType);
|
|
28
15
|
export const path = pathSource(imageType);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Timeout budgets for provider SDK calls (OpenAI has its own in openai_client.ts).
|
|
2
|
+
// Generous safety nets against a stalled connection or a never-completing
|
|
3
|
+
// operation — NOT tight deadlines. A stall becomes a rejection so the caller's
|
|
4
|
+
// GraphAI `retry` can recover instead of hanging forever.
|
|
5
|
+
export const GENAI_REQUEST_TIMEOUT_MS = 120_000; // per @google/genai API call (kick-off / each poll)
|
|
6
|
+
export const REPLICATE_RUN_TIMEOUT_MS = 600_000; // whole replicate.run() job (video models run minutes)
|
|
7
|
+
export const VIDEO_POLL_TIMEOUT_MS = 1_200_000; // wall-clock cap for a long-running Veo video operation
|
|
@@ -49,6 +49,24 @@ export interface SwipeElement {
|
|
|
49
49
|
loop?: SwipeLoop;
|
|
50
50
|
elements?: SwipeElement[];
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The element id both the markup and the generated animation script use. It reaches an HTML
|
|
54
|
+
* attribute AND a raw CSS selector inside a JavaScript string literal
|
|
55
|
+
* (`animation.animate('#id')`), so it is validated rather than escaped — escaping one
|
|
56
|
+
* context leaves the other wrong, and a quote in an id silently produces broken JavaScript
|
|
57
|
+
* instead of a diagnosable error.
|
|
58
|
+
*
|
|
59
|
+
* Not a privilege boundary: `html_tailwind` accepts an author's own `script` by design, so
|
|
60
|
+
* an author already has that capability. This is here so the two contexts agree.
|
|
61
|
+
*/
|
|
62
|
+
/**
|
|
63
|
+
* Bridges a TypeScript gap, not a validation gap: `swipeElementSchema` is declared
|
|
64
|
+
* `z.ZodType` because it is recursive through `z.lazy`, so `z.array(...)` infers `unknown[]`
|
|
65
|
+
* and the shape zod already checked at parse time is lost to the compiler. Every field of
|
|
66
|
+
* SwipeElement is optional, so any object satisfies it structurally — which is what makes
|
|
67
|
+
* this a sound narrowing rather than a cast wearing a guard's clothes.
|
|
68
|
+
*/
|
|
69
|
+
export declare const isSwipeElements: (value: unknown) => value is SwipeElement[];
|
|
52
70
|
/** Generate HTML from Swipe elements */
|
|
53
71
|
export declare const swipeElementsToHtml: (elements: SwipeElement[]) => string;
|
|
54
72
|
/** Generate render() script from Swipe element animations */
|
|
@@ -1,6 +1,30 @@
|
|
|
1
|
+
import { assertSafeElementId } from "./element_id.js";
|
|
1
2
|
const escapeHtml = (str) => {
|
|
2
3
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3
4
|
};
|
|
5
|
+
/**
|
|
6
|
+
* The element id both the markup and the generated animation script use. It reaches an HTML
|
|
7
|
+
* attribute AND a raw CSS selector inside a JavaScript string literal
|
|
8
|
+
* (`animation.animate('#id')`), so it is validated rather than escaped — escaping one
|
|
9
|
+
* context leaves the other wrong, and a quote in an id silently produces broken JavaScript
|
|
10
|
+
* instead of a diagnosable error.
|
|
11
|
+
*
|
|
12
|
+
* Not a privilege boundary: `html_tailwind` accepts an author's own `script` by design, so
|
|
13
|
+
* an author already has that capability. This is here so the two contexts agree.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Bridges a TypeScript gap, not a validation gap: `swipeElementSchema` is declared
|
|
17
|
+
* `z.ZodType` because it is recursive through `z.lazy`, so `z.array(...)` infers `unknown[]`
|
|
18
|
+
* and the shape zod already checked at parse time is lost to the compiler. Every field of
|
|
19
|
+
* SwipeElement is optional, so any object satisfies it structurally — which is what makes
|
|
20
|
+
* this a sound narrowing rather than a cast wearing a guard's clothes.
|
|
21
|
+
*/
|
|
22
|
+
export const isSwipeElements = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "object" && entry !== null);
|
|
23
|
+
const elementId = (el, index) => {
|
|
24
|
+
const id = el.id ?? `swipe_el_${index}`;
|
|
25
|
+
assertSafeElementId(id);
|
|
26
|
+
return id;
|
|
27
|
+
};
|
|
4
28
|
const toCssValue = (value) => {
|
|
5
29
|
return typeof value === "number" ? `${value}px` : value;
|
|
6
30
|
};
|
|
@@ -69,7 +93,7 @@ const buildTextStyle = (el) => {
|
|
|
69
93
|
return styles.join(" ");
|
|
70
94
|
};
|
|
71
95
|
const elementToHtml = (el, index) => {
|
|
72
|
-
const id = el
|
|
96
|
+
const id = elementId(el, index);
|
|
73
97
|
const style = buildElementStyle(el);
|
|
74
98
|
const textStyle = buildTextStyle(el);
|
|
75
99
|
const lines = [];
|
|
@@ -96,7 +120,7 @@ export const swipeElementsToHtml = (elements) => {
|
|
|
96
120
|
};
|
|
97
121
|
const collectAnimations = (elements, entries, indexBase = 0) => {
|
|
98
122
|
elements.forEach((el, i) => {
|
|
99
|
-
const id = el
|
|
123
|
+
const id = elementId(el, indexBase + i);
|
|
100
124
|
if (el.to || el.loop) {
|
|
101
125
|
entries.push({ id, to: el.to, loop: el.loop });
|
|
102
126
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mulmocast",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.node.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"resolutions": {
|
|
27
|
-
"minimatch": "^10.2.
|
|
27
|
+
"minimatch": "^10.2.6",
|
|
28
28
|
"tar": "7.5.22",
|
|
29
29
|
"yauzl": "^3.4.0"
|
|
30
30
|
},
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"homepage": "https://github.com/receptron/mulmocast-cli#readme",
|
|
90
90
|
"dependencies": {
|
|
91
91
|
"@google-cloud/text-to-speech": "^6.4.1",
|
|
92
|
-
"@google/genai": "^2.
|
|
92
|
+
"@google/genai": "^2.15.0",
|
|
93
93
|
"@graphai/anthropic_agent": "^2.0.12",
|
|
94
94
|
"@graphai/browserless_agent": "^2.0.3",
|
|
95
95
|
"@graphai/gemini_agent": "^2.0.5",
|
|
@@ -102,9 +102,9 @@
|
|
|
102
102
|
"@inquirer/input": "^5.1.2",
|
|
103
103
|
"@inquirer/select": "^5.2.1",
|
|
104
104
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
105
|
-
"@modernized/fluent-ffmpeg": "^1.0.
|
|
105
|
+
"@modernized/fluent-ffmpeg": "^1.0.3",
|
|
106
106
|
"@mozilla/readability": "^0.6.0",
|
|
107
|
-
"@mulmocast/deck": "^
|
|
107
|
+
"@mulmocast/deck": "^2.0.0",
|
|
108
108
|
"@tavily/core": "^0.7.6",
|
|
109
109
|
"archiver": "^8.0.0",
|
|
110
110
|
"clipboardy": "^5.3.2",
|
|
@@ -115,10 +115,10 @@
|
|
|
115
115
|
"marked": "^18.0.7",
|
|
116
116
|
"mulmocast-vision": "^1.0.12",
|
|
117
117
|
"ora": "^9.4.1",
|
|
118
|
-
"puppeteer": "^25.
|
|
118
|
+
"puppeteer": "^25.4.0",
|
|
119
119
|
"replicate": "^1.4.0",
|
|
120
120
|
"yaml": "^2.9.0",
|
|
121
|
-
"yargs": "^18.
|
|
121
|
+
"yargs": "^18.1.0",
|
|
122
122
|
"zod": "^4.4.3"
|
|
123
123
|
},
|
|
124
124
|
"devDependencies": {
|
|
@@ -128,16 +128,17 @@
|
|
|
128
128
|
"@types/jsdom": "^28.0.3",
|
|
129
129
|
"@types/yargs": "^17.0.35",
|
|
130
130
|
"cross-env": "^10.1.0",
|
|
131
|
+
"esbuild": "^0.28.2",
|
|
131
132
|
"eslint": "^10.8.0",
|
|
132
133
|
"eslint-config-prettier": "^10.1.8",
|
|
133
134
|
"eslint-plugin-import": "^2.32.0",
|
|
134
135
|
"eslint-plugin-prettier": "^5.5.6",
|
|
135
136
|
"eslint-plugin-sonarjs": "^4.2.0",
|
|
136
|
-
"globals": "^17.
|
|
137
|
+
"globals": "^17.9.0",
|
|
137
138
|
"prettier": "^3.9.6",
|
|
138
|
-
"tsx": "^4.23.
|
|
139
|
+
"tsx": "^4.23.5",
|
|
139
140
|
"typescript": "6.0.3",
|
|
140
|
-
"typescript-eslint": "^8.
|
|
141
|
+
"typescript-eslint": "^8.66.0"
|
|
141
142
|
},
|
|
142
143
|
"engines": {
|
|
143
144
|
"node": ">=22.0.0"
|