@mulmocast/slide 0.4.1 → 0.5.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/.claude/skills/md-to-mulmo/SKILL.md +172 -0
- package/README.md +47 -1
- package/lib/actions/md-to-extended.d.ts +54 -0
- package/lib/actions/md-to-extended.d.ts.map +1 -0
- package/lib/actions/md-to-extended.js +176 -0
- package/lib/actions/md-to-extended.js.map +1 -0
- package/lib/cli.js +20 -0
- package/lib/cli.js.map +1 -1
- package/lib/convert/pdfvision.d.ts +14 -0
- package/lib/convert/pdfvision.d.ts.map +1 -0
- package/lib/convert/pdfvision.js +247 -0
- package/lib/convert/pdfvision.js.map +1 -0
- package/lib/utils/document-analysis.d.ts +43 -0
- package/lib/utils/document-analysis.d.ts.map +1 -0
- package/lib/utils/document-analysis.js +118 -0
- package/lib/utils/document-analysis.js.map +1 -0
- package/lib/utils/markdown-parser.d.ts +28 -0
- package/lib/utils/markdown-parser.d.ts.map +1 -0
- package/lib/utils/markdown-parser.js +215 -0
- package/lib/utils/markdown-parser.js.map +1 -0
- package/lib/utils/narration-generator.d.ts +14 -0
- package/lib/utils/narration-generator.d.ts.map +1 -0
- package/lib/utils/narration-generator.js +68 -0
- package/lib/utils/narration-generator.js.map +1 -0
- package/lib/utils/vision-provider.d.ts +12 -0
- package/lib/utils/vision-provider.d.ts.map +1 -0
- package/lib/utils/vision-provider.js +105 -0
- package/lib/utils/vision-provider.js.map +1 -0
- package/package.json +7 -8
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown Structure Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses markdown into a structured representation for LLM-based
|
|
5
|
+
* presentation planning. Unlike the slide-splitting markdown plugins,
|
|
6
|
+
* this parser preserves the full document structure (heading hierarchy,
|
|
7
|
+
* element types) for intelligent beat allocation by an LLM.
|
|
8
|
+
*/
|
|
9
|
+
const FRONTMATTER_REGEX = /^---\n([\s\S]*?)\n---\n/;
|
|
10
|
+
const HEADING_REGEX = /^(#{1,6})\s+(.+)$/;
|
|
11
|
+
const FENCED_CODE_REGEX = /^```(\w*)\s*$/;
|
|
12
|
+
const TABLE_ROW_REGEX = /^\|.+\|$/;
|
|
13
|
+
const IMAGE_REGEX = /^!\[([^\]]*)\]\(([^)]+)\)$/;
|
|
14
|
+
const CITATION_REGEX = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/;
|
|
15
|
+
const LIST_ITEM_REGEX = /^(\s*[-*+]|\s*\d+\.)\s/;
|
|
16
|
+
const parseFrontmatter = (markdown) => {
|
|
17
|
+
const match = markdown.match(FRONTMATTER_REGEX);
|
|
18
|
+
if (!match) {
|
|
19
|
+
return { frontmatter: null, body: markdown };
|
|
20
|
+
}
|
|
21
|
+
const yamlBlock = match[1];
|
|
22
|
+
const frontmatter = {};
|
|
23
|
+
yamlBlock.split("\n").forEach((line) => {
|
|
24
|
+
const colonIndex = line.indexOf(":");
|
|
25
|
+
if (colonIndex > 0) {
|
|
26
|
+
const key = line.slice(0, colonIndex).trim();
|
|
27
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
28
|
+
frontmatter[key] = value;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return { frontmatter, body: markdown.slice(match[0].length) };
|
|
32
|
+
};
|
|
33
|
+
const extractCitations = (text) => {
|
|
34
|
+
const citations = [];
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
let match;
|
|
37
|
+
const regex = new RegExp(CITATION_REGEX.source, "g");
|
|
38
|
+
while ((match = regex.exec(text)) !== null) {
|
|
39
|
+
const url = match[2];
|
|
40
|
+
if (!seen.has(url)) {
|
|
41
|
+
seen.add(url);
|
|
42
|
+
citations.push({ type: "citation", content: match[1], url });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return citations;
|
|
46
|
+
};
|
|
47
|
+
const collectWhile = (lines, startIndex, predicate) => {
|
|
48
|
+
const collected = [];
|
|
49
|
+
let i = startIndex;
|
|
50
|
+
while (i < lines.length && predicate(lines[i], collected.length, lines[i + 1])) {
|
|
51
|
+
collected.push(lines[i]);
|
|
52
|
+
i++;
|
|
53
|
+
}
|
|
54
|
+
return { collected, endIndex: i };
|
|
55
|
+
};
|
|
56
|
+
const collectTableLines = (lines, startIndex) => {
|
|
57
|
+
const { collected, endIndex } = collectWhile(lines, startIndex, (line) => TABLE_ROW_REGEX.test(line.trim()));
|
|
58
|
+
return { content: collected.join("\n"), endIndex };
|
|
59
|
+
};
|
|
60
|
+
const isListContinuation = (line, count, peekNext) => {
|
|
61
|
+
if (LIST_ITEM_REGEX.test(line))
|
|
62
|
+
return true;
|
|
63
|
+
if (line.startsWith(" ") && count > 0)
|
|
64
|
+
return true;
|
|
65
|
+
if (line.trim() === "" && peekNext !== undefined && LIST_ITEM_REGEX.test(peekNext))
|
|
66
|
+
return true;
|
|
67
|
+
return false;
|
|
68
|
+
};
|
|
69
|
+
const collectListLines = (lines, startIndex) => {
|
|
70
|
+
const { collected, endIndex } = collectWhile(lines, startIndex, isListContinuation);
|
|
71
|
+
return { content: collected.join("\n"), endIndex };
|
|
72
|
+
};
|
|
73
|
+
const collectCodeBlock = (lines, startIndex, lang) => {
|
|
74
|
+
const codeLines = [];
|
|
75
|
+
let i = startIndex + 1;
|
|
76
|
+
while (i < lines.length && !lines[i].trim().startsWith("```")) {
|
|
77
|
+
codeLines.push(lines[i]);
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
const content = codeLines.join("\n");
|
|
81
|
+
const closed = i < lines.length;
|
|
82
|
+
const element = lang === "mermaid"
|
|
83
|
+
? { type: "mermaid", content }
|
|
84
|
+
: { type: "codeBlock", content, lang: lang || undefined };
|
|
85
|
+
return { element, endIndex: closed ? i + 1 : i };
|
|
86
|
+
};
|
|
87
|
+
const handleCodeBlock = (lines, index) => {
|
|
88
|
+
const codeMatch = lines[index].trim().match(FENCED_CODE_REGEX);
|
|
89
|
+
if (!codeMatch)
|
|
90
|
+
return null;
|
|
91
|
+
const { element, endIndex } = collectCodeBlock(lines, index, codeMatch[1]);
|
|
92
|
+
return { elements: [element], endIndex };
|
|
93
|
+
};
|
|
94
|
+
const handleTable = (lines, index) => {
|
|
95
|
+
if (!TABLE_ROW_REGEX.test(lines[index].trim()))
|
|
96
|
+
return null;
|
|
97
|
+
const { content, endIndex } = collectTableLines(lines, index);
|
|
98
|
+
return { elements: [{ type: "table", content }], endIndex };
|
|
99
|
+
};
|
|
100
|
+
const handleImage = (lines, index) => {
|
|
101
|
+
const trimmed = lines[index].trim();
|
|
102
|
+
const imageMatch = trimmed.match(IMAGE_REGEX);
|
|
103
|
+
if (!imageMatch)
|
|
104
|
+
return null;
|
|
105
|
+
return {
|
|
106
|
+
elements: [{ type: "image", content: trimmed, alt: imageMatch[1], url: imageMatch[2] }],
|
|
107
|
+
endIndex: index + 1,
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
const handleList = (lines, index) => {
|
|
111
|
+
if (!LIST_ITEM_REGEX.test(lines[index]))
|
|
112
|
+
return null;
|
|
113
|
+
const { content, endIndex } = collectListLines(lines, index);
|
|
114
|
+
return { elements: [{ type: "list", content }], endIndex };
|
|
115
|
+
};
|
|
116
|
+
const ELEMENT_HANDLERS = [handleCodeBlock, handleTable, handleImage, handleList];
|
|
117
|
+
const tryHandlers = (lines, index) => {
|
|
118
|
+
for (const handler of ELEMENT_HANDLERS) {
|
|
119
|
+
const result = handler(lines, index);
|
|
120
|
+
if (result)
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
};
|
|
125
|
+
const flushTextBuffer = (buffer) => {
|
|
126
|
+
const text = buffer.join("\n").trim();
|
|
127
|
+
if (text.length === 0)
|
|
128
|
+
return [];
|
|
129
|
+
return [{ type: "text", content: text }, ...extractCitations(text)];
|
|
130
|
+
};
|
|
131
|
+
const parseElements = (bodyLines) => {
|
|
132
|
+
const elements = [];
|
|
133
|
+
const textBuffer = [];
|
|
134
|
+
let i = 0;
|
|
135
|
+
while (i < bodyLines.length) {
|
|
136
|
+
const result = tryHandlers(bodyLines, i);
|
|
137
|
+
if (result) {
|
|
138
|
+
elements.push(...flushTextBuffer(textBuffer));
|
|
139
|
+
textBuffer.length = 0;
|
|
140
|
+
elements.push(...result.elements);
|
|
141
|
+
i = result.endIndex;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
textBuffer.push(bodyLines[i]);
|
|
145
|
+
i++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
elements.push(...flushTextBuffer(textBuffer));
|
|
149
|
+
return elements;
|
|
150
|
+
};
|
|
151
|
+
const generateSectionId = (index) => {
|
|
152
|
+
return `sec-${index}`;
|
|
153
|
+
};
|
|
154
|
+
const splitIntoRawSections = (lines) => {
|
|
155
|
+
const raw = [];
|
|
156
|
+
let currentHeading = "(root)";
|
|
157
|
+
let currentLevel = 0;
|
|
158
|
+
let currentBodyLines = [];
|
|
159
|
+
const flush = () => {
|
|
160
|
+
const hasContent = currentBodyLines.some((line) => line.trim().length > 0);
|
|
161
|
+
if (currentLevel > 0 || hasContent) {
|
|
162
|
+
raw.push({ heading: currentHeading, level: currentLevel, bodyLines: currentBodyLines });
|
|
163
|
+
}
|
|
164
|
+
currentBodyLines = [];
|
|
165
|
+
};
|
|
166
|
+
let inCodeBlock = false;
|
|
167
|
+
lines.forEach((line) => {
|
|
168
|
+
if (FENCED_CODE_REGEX.test(line.trim())) {
|
|
169
|
+
inCodeBlock = !inCodeBlock;
|
|
170
|
+
}
|
|
171
|
+
const headingMatch = !inCodeBlock ? line.match(HEADING_REGEX) : null;
|
|
172
|
+
if (headingMatch) {
|
|
173
|
+
flush();
|
|
174
|
+
currentHeading = headingMatch[2];
|
|
175
|
+
currentLevel = headingMatch[1].length;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
currentBodyLines.push(line);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
flush();
|
|
182
|
+
return raw;
|
|
183
|
+
};
|
|
184
|
+
const toSection = (raw, index) => ({
|
|
185
|
+
id: generateSectionId(index),
|
|
186
|
+
heading: raw.heading,
|
|
187
|
+
level: raw.level,
|
|
188
|
+
elements: parseElements(raw.bodyLines),
|
|
189
|
+
children: [],
|
|
190
|
+
});
|
|
191
|
+
export const parseMarkdown = (markdown) => {
|
|
192
|
+
const { frontmatter, body } = parseFrontmatter(markdown);
|
|
193
|
+
const rawSections = splitIntoRawSections(body.split("\n"));
|
|
194
|
+
const sections = rawSections.map(toSection);
|
|
195
|
+
buildHierarchy(sections);
|
|
196
|
+
return { frontmatter, sections };
|
|
197
|
+
};
|
|
198
|
+
const findDirectChildIds = (sections, parentIndex) => {
|
|
199
|
+
const parentLevel = sections[parentIndex].level;
|
|
200
|
+
const children = [];
|
|
201
|
+
for (let j = parentIndex + 1; j < sections.length; j++) {
|
|
202
|
+
if (sections[j].level <= parentLevel)
|
|
203
|
+
break;
|
|
204
|
+
if (sections[j].level === parentLevel + 1)
|
|
205
|
+
children.push(sections[j].id);
|
|
206
|
+
}
|
|
207
|
+
return children;
|
|
208
|
+
};
|
|
209
|
+
const buildHierarchy = (sections) => {
|
|
210
|
+
sections.forEach((section, i) => {
|
|
211
|
+
if (section.level > 0)
|
|
212
|
+
section.children = findDirectChildIds(sections, i);
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
//# sourceMappingURL=markdown-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-parser.js","sourceRoot":"","sources":["../../src/utils/markdown-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SupportedLang } from "./lang.js";
|
|
2
|
+
import type { DocumentAnalysis } from "./document-analysis.js";
|
|
3
|
+
export interface NarrationInput {
|
|
4
|
+
documentAnalysis: DocumentAnalysis;
|
|
5
|
+
extractedTexts: string[];
|
|
6
|
+
lang: SupportedLang;
|
|
7
|
+
}
|
|
8
|
+
export interface NarrationEntry {
|
|
9
|
+
index: number;
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const buildNarrationPrompt: (input: NarrationInput) => string;
|
|
13
|
+
export declare const parseNarrationResponse: (content: string, slideCount: number) => NarrationEntry[];
|
|
14
|
+
//# sourceMappingURL=narration-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narration-generator.d.ts","sourceRoot":"","sources":["../../src/utils/narration-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC7B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,oBAAoB,GAAI,OAAO,cAAc,KAAG,MA0D5D,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,SAAS,MAAM,EAAE,YAAY,MAAM,KAAG,cAAc,EAa1F,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { getLanguageName, extractJsonFromResponse } from "./llm.js";
|
|
2
|
+
export const buildNarrationPrompt = (input) => {
|
|
3
|
+
const { documentAnalysis, extractedTexts, lang } = input;
|
|
4
|
+
const languageName = getLanguageName(lang);
|
|
5
|
+
const slideSpecs = documentAnalysis.slides
|
|
6
|
+
.map((slide, i) => {
|
|
7
|
+
const sourceTexts = slide.sourcePages
|
|
8
|
+
.map((p) => extractedTexts[p])
|
|
9
|
+
.filter(Boolean)
|
|
10
|
+
.join("\n---\n");
|
|
11
|
+
const truncatedText = sourceTexts.length > 3000 ? sourceTexts.slice(0, 3000) + "..." : sourceTexts;
|
|
12
|
+
const parts = [
|
|
13
|
+
`--- Slide ${i} ---`,
|
|
14
|
+
`Title: ${slide.title}`,
|
|
15
|
+
`Section: ${slide.section}`,
|
|
16
|
+
`Narration hint: ${slide.narrationHint}`,
|
|
17
|
+
];
|
|
18
|
+
if (slide.figureRef) {
|
|
19
|
+
parts.push(`Key visual: ${slide.figureRef}`);
|
|
20
|
+
}
|
|
21
|
+
if (truncatedText) {
|
|
22
|
+
parts.push(`Source text:\n${truncatedText}`);
|
|
23
|
+
}
|
|
24
|
+
return parts.join("\n");
|
|
25
|
+
})
|
|
26
|
+
.join("\n\n");
|
|
27
|
+
return `You are a professional presenter creating narration for a presentation.
|
|
28
|
+
|
|
29
|
+
Document: "${documentAnalysis.title}"${documentAnalysis.authors ? ` by ${documentAnalysis.authors}` : ""}
|
|
30
|
+
|
|
31
|
+
Here are the slides with their source content and narration hints:
|
|
32
|
+
|
|
33
|
+
${slideSpecs}
|
|
34
|
+
|
|
35
|
+
Generate narration text for ALL ${documentAnalysis.slides.length} slides.
|
|
36
|
+
|
|
37
|
+
Requirements:
|
|
38
|
+
- Write in ${languageName}
|
|
39
|
+
- Speak directly to the audience as if presenting live
|
|
40
|
+
- NEVER use meta-references like "this slide shows", "here we see", "このスライドでは"
|
|
41
|
+
- When a slide has a key visual (figure/table/chart), explain what it shows and why it matters
|
|
42
|
+
- Flow naturally between slides as a coherent presentation
|
|
43
|
+
- Be substantive - explain concepts, don't just list bullet points
|
|
44
|
+
- Use a confident, engaging speaking style suitable for text-to-speech
|
|
45
|
+
- Each narration should be 2-5 sentences
|
|
46
|
+
|
|
47
|
+
Respond in JSON:
|
|
48
|
+
{
|
|
49
|
+
"narrations": [
|
|
50
|
+
{"index": 0, "text": "narration text"},
|
|
51
|
+
{"index": 1, "text": "narration text"}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Respond ONLY with valid JSON.`;
|
|
56
|
+
};
|
|
57
|
+
export const parseNarrationResponse = (content, slideCount) => {
|
|
58
|
+
const jsonStr = extractJsonFromResponse(content);
|
|
59
|
+
const parsed = JSON.parse(jsonStr);
|
|
60
|
+
const narrations = (parsed.narrations ?? []).map((n) => ({
|
|
61
|
+
index: Number(n.index ?? 0),
|
|
62
|
+
text: String(n.text ?? ""),
|
|
63
|
+
}));
|
|
64
|
+
// Fill missing indices with empty text
|
|
65
|
+
const resultMap = new Map(narrations.map((n) => [n.index, n]));
|
|
66
|
+
return Array.from({ length: slideCount }, (_, i) => resultMap.get(i) ?? { index: i, text: "" });
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=narration-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narration-generator.js","sourceRoot":"","sources":["../../src/utils/narration-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type VisionProvider = "gemini" | "openai";
|
|
2
|
+
export interface VisionImage {
|
|
3
|
+
path: string;
|
|
4
|
+
}
|
|
5
|
+
export interface VisionRequest {
|
|
6
|
+
prompt: string;
|
|
7
|
+
images: VisionImage[];
|
|
8
|
+
}
|
|
9
|
+
export declare const resolveVisionProvider: (preferred?: string) => VisionProvider;
|
|
10
|
+
export declare const callVisionAPI: (provider: VisionProvider, request: VisionRequest) => Promise<string>;
|
|
11
|
+
export declare const callTextLLM: (provider: VisionProvider, prompt: string) => Promise<string>;
|
|
12
|
+
//# sourceMappingURL=vision-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vision-provider.d.ts","sourceRoot":"","sources":["../../src/utils/vision-provider.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAOD,eAAO,MAAM,qBAAqB,GAAI,YAAY,MAAM,KAAG,cAkB1D,CAAC;AA8DF,eAAO,MAAM,aAAa,GACxB,UAAU,cAAc,EACxB,SAAS,aAAa,KACrB,OAAO,CAAC,MAAM,CAOhB,CAAC;AA2BF,eAAO,MAAM,WAAW,GAAU,UAAU,cAAc,EAAE,QAAQ,MAAM,KAAG,OAAO,CAAC,MAAM,CAO1F,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
2
|
+
import { getOpenAIClient, imageToBase64, getImageMediaType, extractResponseContent, } from "./llm.js";
|
|
3
|
+
const GEMINI_VISION_MODEL = "gemini-2.0-flash";
|
|
4
|
+
const GEMINI_TEXT_MODEL = "gemini-2.0-flash";
|
|
5
|
+
const OPENAI_VISION_MODEL = "gpt-4o";
|
|
6
|
+
const OPENAI_TEXT_MODEL = "gpt-4o";
|
|
7
|
+
export const resolveVisionProvider = (preferred) => {
|
|
8
|
+
if (preferred) {
|
|
9
|
+
if (preferred !== "gemini" && preferred !== "openai") {
|
|
10
|
+
throw new Error(`Unknown provider: ${preferred}. Use 'gemini' or 'openai'.`);
|
|
11
|
+
}
|
|
12
|
+
return preferred;
|
|
13
|
+
}
|
|
14
|
+
if (process.env.GEMINI_API_KEY) {
|
|
15
|
+
return "gemini";
|
|
16
|
+
}
|
|
17
|
+
if (process.env.OPENAI_API_KEY) {
|
|
18
|
+
return "openai";
|
|
19
|
+
}
|
|
20
|
+
throw new Error("No Vision API key found. Set GEMINI_API_KEY or OPENAI_API_KEY environment variable.");
|
|
21
|
+
};
|
|
22
|
+
const getGeminiClient = () => {
|
|
23
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
24
|
+
if (!apiKey) {
|
|
25
|
+
throw new Error("GEMINI_API_KEY environment variable is not set");
|
|
26
|
+
}
|
|
27
|
+
return new GoogleGenerativeAI(apiKey);
|
|
28
|
+
};
|
|
29
|
+
const callGeminiVision = async (request) => {
|
|
30
|
+
const genAI = getGeminiClient();
|
|
31
|
+
const model = genAI.getGenerativeModel({ model: GEMINI_VISION_MODEL });
|
|
32
|
+
const imageParts = request.images.map((img) => {
|
|
33
|
+
const base64 = imageToBase64(img.path);
|
|
34
|
+
const mimeType = getImageMediaType(img.path);
|
|
35
|
+
return {
|
|
36
|
+
inlineData: { data: base64, mimeType },
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
const result = await model.generateContent([request.prompt, ...imageParts]);
|
|
40
|
+
const response = result.response;
|
|
41
|
+
const text = response.text();
|
|
42
|
+
if (!text) {
|
|
43
|
+
throw new Error("No response from Gemini Vision API");
|
|
44
|
+
}
|
|
45
|
+
return text;
|
|
46
|
+
};
|
|
47
|
+
const callOpenAIVision = async (request) => {
|
|
48
|
+
const client = getOpenAIClient();
|
|
49
|
+
const imageContents = request.images.flatMap((img) => {
|
|
50
|
+
const base64 = imageToBase64(img.path);
|
|
51
|
+
const mediaType = getImageMediaType(img.path);
|
|
52
|
+
return [
|
|
53
|
+
{
|
|
54
|
+
type: "image_url",
|
|
55
|
+
image_url: { url: `data:${mediaType};base64,${base64}`, detail: "auto" },
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
});
|
|
59
|
+
const content = [
|
|
60
|
+
{ type: "text", text: request.prompt },
|
|
61
|
+
...imageContents,
|
|
62
|
+
];
|
|
63
|
+
const response = await client.chat.completions.create({
|
|
64
|
+
model: OPENAI_VISION_MODEL,
|
|
65
|
+
messages: [{ role: "user", content }],
|
|
66
|
+
response_format: { type: "json_object" },
|
|
67
|
+
});
|
|
68
|
+
return extractResponseContent(response);
|
|
69
|
+
};
|
|
70
|
+
export const callVisionAPI = async (provider, request) => {
|
|
71
|
+
switch (provider) {
|
|
72
|
+
case "gemini":
|
|
73
|
+
return callGeminiVision(request);
|
|
74
|
+
case "openai":
|
|
75
|
+
return callOpenAIVision(request);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const callGeminiText = async (prompt) => {
|
|
79
|
+
const genAI = getGeminiClient();
|
|
80
|
+
const model = genAI.getGenerativeModel({ model: GEMINI_TEXT_MODEL });
|
|
81
|
+
const result = await model.generateContent(prompt);
|
|
82
|
+
const text = result.response.text();
|
|
83
|
+
if (!text) {
|
|
84
|
+
throw new Error("No response from Gemini Text API");
|
|
85
|
+
}
|
|
86
|
+
return text;
|
|
87
|
+
};
|
|
88
|
+
const callOpenAIText = async (prompt) => {
|
|
89
|
+
const client = getOpenAIClient();
|
|
90
|
+
const response = await client.chat.completions.create({
|
|
91
|
+
model: OPENAI_TEXT_MODEL,
|
|
92
|
+
messages: [{ role: "user", content: prompt }],
|
|
93
|
+
response_format: { type: "json_object" },
|
|
94
|
+
});
|
|
95
|
+
return extractResponseContent(response);
|
|
96
|
+
};
|
|
97
|
+
export const callTextLLM = async (provider, prompt) => {
|
|
98
|
+
switch (provider) {
|
|
99
|
+
case "gemini":
|
|
100
|
+
return callGeminiText(prompt);
|
|
101
|
+
case "openai":
|
|
102
|
+
return callOpenAIText(prompt);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=vision-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vision-provider.js","sourceRoot":"","sources":["../../src/utils/vision-provider.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mulmocast/slide",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Convert presentations (Keynote, PowerPoint, PDF, Marp) and videos to MulmoScript format",
|
|
5
5
|
"homepage": "https://github.com/receptron/MulmoCast-Slides#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
"transcribe": "tsx src/cli.ts transcribe",
|
|
55
55
|
"movie": "tsx src/cli.ts movie",
|
|
56
56
|
"bundle": "tsx src/cli.ts bundle",
|
|
57
|
+
"parse-md": "tsx src/cli.ts parse-md",
|
|
58
|
+
"assemble-extended": "tsx src/cli.ts assemble-extended",
|
|
57
59
|
"narrate": "tsx src/cli.ts narrate",
|
|
58
60
|
"upload": "tsx src/cli.ts upload",
|
|
59
61
|
"test:keynote": "tsx src/cli.ts keynote samples/GraphAI.key",
|
|
@@ -72,15 +74,15 @@
|
|
|
72
74
|
"pdf-parse": "^2.4.5",
|
|
73
75
|
"ppt-png": "^2.2.0",
|
|
74
76
|
"tsx": "^4.7.0",
|
|
75
|
-
"vue": "^3.5.
|
|
77
|
+
"vue": "^3.5.28",
|
|
76
78
|
"yargs": "^18.0.0"
|
|
77
79
|
},
|
|
78
80
|
"devDependencies": {
|
|
79
81
|
"@tailwindcss/vite": "^4.1.18",
|
|
80
|
-
"@types/node": "^25.2.
|
|
82
|
+
"@types/node": "^25.2.2",
|
|
81
83
|
"@types/yargs": "^17.0.35",
|
|
82
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
83
|
-
"@typescript-eslint/parser": "^8.
|
|
84
|
+
"@typescript-eslint/eslint-plugin": "^8.55.0",
|
|
85
|
+
"@typescript-eslint/parser": "^8.55.0",
|
|
84
86
|
"@vitejs/plugin-vue": "^6.0.4",
|
|
85
87
|
"eslint": "^9.39.2",
|
|
86
88
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -90,8 +92,5 @@
|
|
|
90
92
|
"tailwindcss": "^4.1.18",
|
|
91
93
|
"typescript": "^5.9.3",
|
|
92
94
|
"vite": "^7.3.1"
|
|
93
|
-
},
|
|
94
|
-
"resolutions": {
|
|
95
|
-
"unicorn-magic": "0.2.0"
|
|
96
95
|
}
|
|
97
96
|
}
|