mulmocast 2.6.20 → 2.6.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/lib/actions/audio.js +3 -0
- package/lib/actions/image_references.js +3 -0
- package/lib/actions/images.js +3 -0
- package/lib/actions/translate.d.ts +2 -0
- package/lib/actions/translate.js +5 -0
- package/lib/agents/image_genai_agent.js +12 -3
- package/lib/agents/image_openai_agent.js +20 -5
- package/lib/agents/image_replicate_agent.js +9 -3
- package/lib/agents/lipsync_replicate_agent.js +10 -5
- package/lib/agents/movie_genai_agent.js +22 -4
- package/lib/agents/movie_replicate_agent.js +16 -5
- package/lib/agents/sound_effect_replicate_agent.js +4 -4
- package/lib/agents/tts_elevenlabs_agent.js +12 -1
- package/lib/agents/tts_gemini_agent.js +59 -32
- package/lib/agents/tts_google_agent.js +9 -1
- package/lib/agents/tts_kotodama_agent.js +9 -1
- package/lib/agents/tts_openai_agent.js +9 -1
- package/lib/cli/commands/audio/handler.js +2 -1
- package/lib/cli/commands/bundle/handler.js +2 -1
- package/lib/cli/commands/html/handler.js +2 -1
- package/lib/cli/commands/image/handler.js +2 -1
- package/lib/cli/commands/markdown/handler.js +2 -1
- package/lib/cli/commands/movie/handler.js +2 -1
- package/lib/cli/commands/pdf/handler.js +2 -1
- package/lib/cli/commands/tool/whisper/handler.js +60 -37
- package/lib/cli/commands/translate/handler.js +2 -1
- package/lib/cli/helpers.d.ts +1 -0
- package/lib/cli/helpers.js +46 -0
- package/lib/tools/create_mulmo_script_from_url.d.ts +2 -2
- package/lib/tools/create_mulmo_script_from_url.js +5 -2
- package/lib/tools/create_mulmo_script_interactively.d.ts +1 -1
- package/lib/tools/create_mulmo_script_interactively.js +6 -3
- package/lib/tools/deep_research.d.ts +2 -1
- package/lib/tools/deep_research.js +3 -1
- package/lib/tools/story_to_script.d.ts +3 -1
- package/lib/tools/story_to_script.js +3 -1
- package/lib/types/agent.d.ts +2 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/lib/types/type.d.ts +3 -0
- package/lib/types/usage.d.ts +32 -0
- package/lib/types/usage.js +1 -0
- package/lib/utils/context.d.ts +2 -0
- package/lib/utils/context.js +2 -0
- package/lib/utils/filters.js +3 -6
- package/lib/utils/replicate_usage.d.ts +5 -0
- package/lib/utils/replicate_usage.js +11 -0
- package/lib/utils/usage_callback.d.ts +4 -0
- package/lib/utils/usage_callback.js +93 -0
- package/lib/utils/usage_collector.d.ts +12 -0
- package/lib/utils/usage_collector.js +18 -0
- package/package.json +6 -6
|
@@ -44,7 +44,15 @@ export const ttsKotodamaAgent = async ({ namedInputs, params, config, }) => {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
const buffer = Buffer.from(json.audios[0], "base64");
|
|
47
|
-
|
|
47
|
+
// Kotodama is subscription-billed externally (no public per-char rate API
|
|
48
|
+
// surface). Report inputChars + voice/decoration so the billing layer can
|
|
49
|
+
// do its own accounting.
|
|
50
|
+
const usage = {
|
|
51
|
+
provider: "kotodama",
|
|
52
|
+
model: body.speaker_id,
|
|
53
|
+
inputChars: text.length,
|
|
54
|
+
};
|
|
55
|
+
return { buffer, usage };
|
|
48
56
|
}
|
|
49
57
|
catch (error) {
|
|
50
58
|
if (suppressError) {
|
|
@@ -28,7 +28,15 @@ export const ttsOpenaiAgent = async ({ namedInputs, params, config, }) => {
|
|
|
28
28
|
GraphAILogger.log("ttsOptions", tts_options);
|
|
29
29
|
const response = await openai.audio.speech.create(tts_options);
|
|
30
30
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
31
|
-
|
|
31
|
+
// tts-1 / tts-1-hd bill per character. gpt-4o-mini-tts is token-based and
|
|
32
|
+
// is handled by #1428; for now we report inputChars uniformly and let the
|
|
33
|
+
// billing layer pick the right unit from `model`.
|
|
34
|
+
const usage = {
|
|
35
|
+
provider: "openai",
|
|
36
|
+
model: tts_options.model,
|
|
37
|
+
inputChars: text.length,
|
|
38
|
+
};
|
|
39
|
+
return { buffer, usage };
|
|
32
40
|
}
|
|
33
41
|
catch (error) {
|
|
34
42
|
if (suppressError) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { audio } from "../../../actions/index.js";
|
|
2
|
-
import { initializeContext, runTranslateIfNeeded } from "../../helpers.js";
|
|
2
|
+
import { dumpUsageIfRequested, initializeContext, runTranslateIfNeeded } from "../../helpers.js";
|
|
3
3
|
export const handler = async (argv) => {
|
|
4
4
|
const context = await initializeContext(argv);
|
|
5
5
|
if (!context) {
|
|
@@ -7,4 +7,5 @@ export const handler = async (argv) => {
|
|
|
7
7
|
}
|
|
8
8
|
await runTranslateIfNeeded(context);
|
|
9
9
|
await audio(context);
|
|
10
|
+
dumpUsageIfRequested(context);
|
|
10
11
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mulmoViewerBundle, audio, images, translate } from "../../../actions/index.js";
|
|
2
|
-
import { initializeContext } from "../../helpers.js";
|
|
2
|
+
import { dumpUsageIfRequested, initializeContext } from "../../helpers.js";
|
|
3
3
|
import { bundleTargetLang } from "../../../types/const.js";
|
|
4
4
|
export const handler = async (argv) => {
|
|
5
5
|
const context = await initializeContext(argv);
|
|
@@ -11,4 +11,5 @@ export const handler = async (argv) => {
|
|
|
11
11
|
await audio({ ...context, lang });
|
|
12
12
|
}
|
|
13
13
|
await audio(context).then(images).then(mulmoViewerBundle);
|
|
14
|
+
dumpUsageIfRequested(context);
|
|
14
15
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { images, html } from "../../../actions/index.js";
|
|
2
|
-
import { initializeContext, runTranslateIfNeeded } from "../../helpers.js";
|
|
2
|
+
import { dumpUsageIfRequested, initializeContext, runTranslateIfNeeded } from "../../helpers.js";
|
|
3
3
|
export const handler = async (argv) => {
|
|
4
4
|
const context = await initializeContext(argv);
|
|
5
5
|
if (!context) {
|
|
@@ -8,4 +8,5 @@ export const handler = async (argv) => {
|
|
|
8
8
|
await runTranslateIfNeeded(context);
|
|
9
9
|
await images(context);
|
|
10
10
|
await html(context, argv.image_width);
|
|
11
|
+
dumpUsageIfRequested(context);
|
|
11
12
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { images } from "../../../actions/index.js";
|
|
2
|
-
import { initializeContext, runTranslateIfNeeded } from "../../helpers.js";
|
|
2
|
+
import { dumpUsageIfRequested, initializeContext, runTranslateIfNeeded } from "../../helpers.js";
|
|
3
3
|
export const handler = async (argv) => {
|
|
4
4
|
const context = await initializeContext(argv);
|
|
5
5
|
if (!context) {
|
|
@@ -7,4 +7,5 @@ export const handler = async (argv) => {
|
|
|
7
7
|
}
|
|
8
8
|
await runTranslateIfNeeded(context);
|
|
9
9
|
await images(context);
|
|
10
|
+
dumpUsageIfRequested(context);
|
|
10
11
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { images, markdown } from "../../../actions/index.js";
|
|
2
|
-
import { initializeContext, runTranslateIfNeeded } from "../../helpers.js";
|
|
2
|
+
import { dumpUsageIfRequested, initializeContext, runTranslateIfNeeded } from "../../helpers.js";
|
|
3
3
|
export const handler = async (argv) => {
|
|
4
4
|
const context = await initializeContext(argv);
|
|
5
5
|
if (!context) {
|
|
@@ -8,4 +8,5 @@ export const handler = async (argv) => {
|
|
|
8
8
|
await runTranslateIfNeeded(context);
|
|
9
9
|
await images(context);
|
|
10
10
|
await markdown(context, argv.image_width);
|
|
11
|
+
dumpUsageIfRequested(context);
|
|
11
12
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { audio, images, movie, captions } from "../../../actions/index.js";
|
|
2
|
-
import { initializeContext, runTranslateIfNeeded } from "../../helpers.js";
|
|
2
|
+
import { dumpUsageIfRequested, initializeContext, runTranslateIfNeeded } from "../../helpers.js";
|
|
3
3
|
export const handler = async (argv) => {
|
|
4
4
|
const context = await initializeContext(argv);
|
|
5
5
|
if (!context) {
|
|
@@ -7,4 +7,5 @@ export const handler = async (argv) => {
|
|
|
7
7
|
}
|
|
8
8
|
await runTranslateIfNeeded(context, true);
|
|
9
9
|
await audio(context).then(images).then(captions).then(movie);
|
|
10
|
+
dumpUsageIfRequested(context);
|
|
10
11
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { images, pdf } from "../../../actions/index.js";
|
|
2
|
-
import { initializeContext, runTranslateIfNeeded } from "../../helpers.js";
|
|
2
|
+
import { dumpUsageIfRequested, initializeContext, runTranslateIfNeeded } from "../../helpers.js";
|
|
3
3
|
export const handler = async (argv) => {
|
|
4
4
|
const context = await initializeContext(argv);
|
|
5
5
|
if (!context) {
|
|
@@ -8,4 +8,5 @@ export const handler = async (argv) => {
|
|
|
8
8
|
await runTranslateIfNeeded(context);
|
|
9
9
|
await images(context);
|
|
10
10
|
await pdf(context, argv.pdf_mode, argv.pdf_size);
|
|
11
|
+
dumpUsageIfRequested(context);
|
|
11
12
|
};
|
|
@@ -51,40 +51,63 @@ export const handler = async (argv) => {
|
|
|
51
51
|
GraphAILogger.error("Error: OPENAI_API_KEY environment variable is required");
|
|
52
52
|
process.exit(1);
|
|
53
53
|
}
|
|
54
|
-
try
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
*/
|
|
83
|
-
};
|
|
54
|
+
// Each phase has its own try/catch so a failure points at the right
|
|
55
|
+
// subsystem instead of being collapsed into "Error transcribing audio"
|
|
56
|
+
// (same anti-pattern as #1451 in tts_gemini_agent.ts — an ffmpeg
|
|
57
|
+
// SIGABRT or a permission-denied write would otherwise read as an
|
|
58
|
+
// OpenAI-side failure).
|
|
59
|
+
// Phase 1 — ffmpeg: read the audio file's duration.
|
|
60
|
+
const audioDuration = await (async () => {
|
|
61
|
+
try {
|
|
62
|
+
const { duration } = await ffmpegGetMediaDuration(fullPath);
|
|
63
|
+
return duration;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
GraphAILogger.error("Error reading audio duration with ffmpeg:", error);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
})();
|
|
70
|
+
GraphAILogger.info(`Audio duration: ${audioDuration.toFixed(2)} seconds`);
|
|
71
|
+
// Phase 2 — OpenAI: Whisper transcription API call.
|
|
72
|
+
const transcription = await (async () => {
|
|
73
|
+
try {
|
|
74
|
+
const openai = new OpenAI({ apiKey });
|
|
75
|
+
return await openai.audio.transcriptions.create({
|
|
76
|
+
file: createReadStream(fullPath),
|
|
77
|
+
model: "whisper-1",
|
|
78
|
+
response_format: "verbose_json",
|
|
79
|
+
timestamp_granularities: ["word", "segment"],
|
|
84
80
|
});
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
GraphAILogger.error("Error calling OpenAI transcription API:", error);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
})();
|
|
87
|
+
if (transcription.segments) {
|
|
88
|
+
const starts = transcription.segments.map((segment) => segment.start);
|
|
89
|
+
starts[0] = 0;
|
|
90
|
+
starts.push(audioDuration);
|
|
91
|
+
// Create beats from transcription segments
|
|
92
|
+
const beats = transcription.segments.map((segment, index) => {
|
|
93
|
+
const duration = Math.round((starts[index + 1] - starts[index]) * 100) / 100;
|
|
94
|
+
return {
|
|
95
|
+
text: segment.text,
|
|
96
|
+
duration,
|
|
97
|
+
/*
|
|
98
|
+
image: {
|
|
99
|
+
type: "textSlide",
|
|
100
|
+
slide: {
|
|
101
|
+
title: "Place Holder",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
*/
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
// Create the script with the processed beats
|
|
108
|
+
const script = createMulmoScript(fullPath, beats);
|
|
109
|
+
// Phase 3 — fs: write the generated script to disk.
|
|
110
|
+
try {
|
|
88
111
|
const outputDir = "output";
|
|
89
112
|
if (!existsSync(outputDir)) {
|
|
90
113
|
mkdirSync(outputDir, { recursive: true });
|
|
@@ -93,9 +116,9 @@ export const handler = async (argv) => {
|
|
|
93
116
|
writeFileSync(outputPath, JSON.stringify(script, null, 2));
|
|
94
117
|
GraphAILogger.info(`Script saved to: ${outputPath}`);
|
|
95
118
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
119
|
+
catch (error) {
|
|
120
|
+
GraphAILogger.error("Error writing transcription output file:", error);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
100
123
|
}
|
|
101
124
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { translate } from "../../../actions/index.js";
|
|
2
|
-
import { initializeContext } from "../../helpers.js";
|
|
2
|
+
import { dumpUsageIfRequested, initializeContext } from "../../helpers.js";
|
|
3
3
|
export const handler = async (argv) => {
|
|
4
4
|
const context = await initializeContext(argv);
|
|
5
5
|
if (!context) {
|
|
6
6
|
process.exit(1);
|
|
7
7
|
}
|
|
8
8
|
await translate(context);
|
|
9
|
+
dumpUsageIfRequested(context);
|
|
9
10
|
};
|
package/lib/cli/helpers.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CliArgs } from "../types/cli_types.js";
|
|
2
2
|
import { FileObject, InitOptions, MulmoStudioContext } from "../types/index.js";
|
|
3
3
|
export declare const runTranslateIfNeeded: (context: MulmoStudioContext, includeCaption?: boolean) => Promise<void>;
|
|
4
|
+
export declare const dumpUsageIfRequested: (context: MulmoStudioContext) => void;
|
|
4
5
|
export declare const setGraphAILogger: (verbose: boolean | undefined, logValues?: Record<string, unknown>) => void;
|
|
5
6
|
export declare const getFileObject: (args: {
|
|
6
7
|
basedir?: string;
|
package/lib/cli/helpers.js
CHANGED
|
@@ -14,6 +14,52 @@ export const runTranslateIfNeeded = async (context, includeCaption = false) => {
|
|
|
14
14
|
await translate(context);
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
|
+
const summarizeUsage = (context) => {
|
|
18
|
+
const snapshot = context.usageCollector?.snapshot() ?? [];
|
|
19
|
+
const groups = new Map();
|
|
20
|
+
for (const record of snapshot) {
|
|
21
|
+
const key = `${record.provider}:${record.model}`;
|
|
22
|
+
const group = groups.get(key) ?? {
|
|
23
|
+
provider: record.provider,
|
|
24
|
+
model: record.model,
|
|
25
|
+
records: 0,
|
|
26
|
+
inputTokens: 0,
|
|
27
|
+
outputTokens: 0,
|
|
28
|
+
totalTokens: 0,
|
|
29
|
+
predictSec: 0,
|
|
30
|
+
inputChars: 0,
|
|
31
|
+
};
|
|
32
|
+
group.records += 1;
|
|
33
|
+
group.inputTokens += record.inputTokens ?? 0;
|
|
34
|
+
group.outputTokens += record.outputTokens ?? 0;
|
|
35
|
+
group.totalTokens += record.totalTokens ?? 0;
|
|
36
|
+
group.predictSec += record.predictSec ?? 0;
|
|
37
|
+
group.inputChars += record.inputChars ?? 0;
|
|
38
|
+
groups.set(key, group);
|
|
39
|
+
}
|
|
40
|
+
return { records: snapshot.length, byModel: Array.from(groups.values()), snapshot };
|
|
41
|
+
};
|
|
42
|
+
// Opt-in usage dump after a CLI action completes. Controlled by
|
|
43
|
+
// MULMOCAST_DUMP_USAGE:
|
|
44
|
+
// - unset / empty → no-op (zero overhead)
|
|
45
|
+
// - "1" / "true" / "stdout" → print JSON to stdout via GraphAILogger.info
|
|
46
|
+
// - any other value → treated as a file path and the JSON is written there
|
|
47
|
+
//
|
|
48
|
+
// The API/billing layer reads context.usageCollector directly; this is for
|
|
49
|
+
// local CLI verification only.
|
|
50
|
+
export const dumpUsageIfRequested = (context) => {
|
|
51
|
+
const setting = process.env.MULMOCAST_DUMP_USAGE;
|
|
52
|
+
if (!setting)
|
|
53
|
+
return;
|
|
54
|
+
const payload = summarizeUsage(context);
|
|
55
|
+
const json = JSON.stringify(payload, null, 2);
|
|
56
|
+
if (setting === "1" || setting === "true" || setting === "stdout") {
|
|
57
|
+
GraphAILogger.info(json);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
fs.writeFileSync(setting, json, "utf-8");
|
|
61
|
+
GraphAILogger.info(`usage written to ${setting}`);
|
|
62
|
+
};
|
|
17
63
|
export const setGraphAILogger = (verbose, logValues) => {
|
|
18
64
|
if (verbose) {
|
|
19
65
|
if (logValues) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { ScriptingParams } from "../types/index.js";
|
|
2
|
-
export declare const createMulmoScriptFromUrl: ({ urls, templateName, outDirPath, filename, cacheDirPath, llm, llm_model }: ScriptingParams) => Promise<void>;
|
|
3
|
-
export declare const createMulmoScriptFromFile: (fileName: string, { templateName, outDirPath, filename, cacheDirPath, llm, llm_model, verbose }: ScriptingParams) => Promise<void>;
|
|
2
|
+
export declare const createMulmoScriptFromUrl: ({ urls, templateName, outDirPath, filename, cacheDirPath, llm, llm_model, usageCollector }: ScriptingParams) => Promise<void>;
|
|
3
|
+
export declare const createMulmoScriptFromFile: (fileName: string, { templateName, outDirPath, filename, cacheDirPath, llm, llm_model, verbose, usageCollector }: ScriptingParams) => Promise<void>;
|
|
@@ -15,6 +15,7 @@ import { mulmoScriptSchema, urlsSchema } from "../types/schema.js";
|
|
|
15
15
|
import { cliLoadingPlugin } from "../utils/plugins.js";
|
|
16
16
|
import { graphDataScriptFromUrlPrompt } from "../utils/prompt.js";
|
|
17
17
|
import { llmPair, settings2GraphAIConfig } from "../utils/utils.js";
|
|
18
|
+
import { createUsageCallback } from "../utils/usage_callback.js";
|
|
18
19
|
import { readFileSync } from "fs";
|
|
19
20
|
dotenv.config({ quiet: true });
|
|
20
21
|
const vanillaAgents = agents.default ?? agents;
|
|
@@ -207,7 +208,7 @@ const graphDataText = {
|
|
|
207
208
|
},
|
|
208
209
|
},
|
|
209
210
|
};
|
|
210
|
-
export const createMulmoScriptFromUrl = async ({ urls, templateName, outDirPath, filename, cacheDirPath, llm, llm_model }) => {
|
|
211
|
+
export const createMulmoScriptFromUrl = async ({ urls, templateName, outDirPath, filename, cacheDirPath, llm, llm_model, usageCollector }) => {
|
|
211
212
|
mkdir(outDirPath);
|
|
212
213
|
mkdir(cacheDirPath);
|
|
213
214
|
const parsedUrls = urlsSchema.parse(urls);
|
|
@@ -239,6 +240,7 @@ export const createMulmoScriptFromUrl = async ({ urls, templateName, outDirPath,
|
|
|
239
240
|
graph.injectValue("llmModel", model);
|
|
240
241
|
graph.injectValue("maxTokens", max_tokens);
|
|
241
242
|
graph.registerCallback(cliLoadingPlugin({ nodeId: "mulmoScript", message: "Generating script..." }));
|
|
243
|
+
graph.registerCallback(createUsageCallback(usageCollector));
|
|
242
244
|
const result = await graph.run();
|
|
243
245
|
if (!result?.writeJSON?.path) {
|
|
244
246
|
showErrorMessage("Script generation failed. Please try again.");
|
|
@@ -246,7 +248,7 @@ export const createMulmoScriptFromUrl = async ({ urls, templateName, outDirPath,
|
|
|
246
248
|
}
|
|
247
249
|
writingMessage(result?.writeJSON?.path ?? "");
|
|
248
250
|
};
|
|
249
|
-
export const createMulmoScriptFromFile = async (fileName, { templateName, outDirPath, filename, cacheDirPath, llm, llm_model, verbose }) => {
|
|
251
|
+
export const createMulmoScriptFromFile = async (fileName, { templateName, outDirPath, filename, cacheDirPath, llm, llm_model, verbose, usageCollector }) => {
|
|
250
252
|
mkdir(outDirPath);
|
|
251
253
|
mkdir(cacheDirPath);
|
|
252
254
|
const filePath = path.resolve(process.cwd(), fileName);
|
|
@@ -272,6 +274,7 @@ export const createMulmoScriptFromFile = async (fileName, { templateName, outDir
|
|
|
272
274
|
if (!verbose) {
|
|
273
275
|
graph.registerCallback(cliLoadingPlugin({ nodeId: "mulmoScript", message: "Generating script..." }));
|
|
274
276
|
}
|
|
277
|
+
graph.registerCallback(createUsageCallback(usageCollector));
|
|
275
278
|
const result = await graph.run();
|
|
276
279
|
if (!result?.writeJSON?.path) {
|
|
277
280
|
showErrorMessage("Script generation failed. Please try again.");
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { ScriptingParams } from "../types/index.js";
|
|
2
|
-
export declare const createMulmoScriptInteractively: ({ outDirPath, cacheDirPath, filename, templateName, urls, llm, llm_model }: ScriptingParams) => Promise<void>;
|
|
2
|
+
export declare const createMulmoScriptInteractively: ({ outDirPath, cacheDirPath, filename, templateName, urls, llm, llm_model, usageCollector, }: ScriptingParams) => Promise<void>;
|
|
@@ -14,6 +14,7 @@ import { mulmoScriptSchema } from "../types/index.js";
|
|
|
14
14
|
import { browserlessAgent } from "@graphai/browserless_agent";
|
|
15
15
|
import validateSchemaAgent from "../agents/validate_schema_agent.js";
|
|
16
16
|
import { llmPair, settings2GraphAIConfig } from "../utils/utils.js";
|
|
17
|
+
import { createUsageCallback } from "../utils/usage_callback.js";
|
|
17
18
|
import { interactiveClarificationPrompt, prefixPrompt } from "../utils/prompt.js";
|
|
18
19
|
// import { cliLoadingPlugin } from "../utils/plugins.js";
|
|
19
20
|
dotenv.config({ quiet: true });
|
|
@@ -199,7 +200,7 @@ const graphData = {
|
|
|
199
200
|
},
|
|
200
201
|
},
|
|
201
202
|
};
|
|
202
|
-
const scrapeWebContent = async (urls, cacheDirPath) => {
|
|
203
|
+
const scrapeWebContent = async (urls, cacheDirPath, usageCollector) => {
|
|
203
204
|
mkdir(cacheDirPath);
|
|
204
205
|
GraphAILogger.info(`${agentHeader} Scraping ${urls.length} URLs...\n`);
|
|
205
206
|
const browserlessCache = browserlessCacheGenerator(cacheDirPath);
|
|
@@ -212,16 +213,17 @@ const scrapeWebContent = async (urls, cacheDirPath) => {
|
|
|
212
213
|
];
|
|
213
214
|
const graph = new GraphAI(graphDataForScraping, { ...vanillaAgents, openAIAgent, textInputAgent, fileWriteAgent, browserlessAgent }, { agentFilters });
|
|
214
215
|
graph.injectValue("urls", urls);
|
|
216
|
+
graph.registerCallback(createUsageCallback(usageCollector));
|
|
215
217
|
const result = (await graph.run());
|
|
216
218
|
if (!result?.sourceText?.text) {
|
|
217
219
|
return "";
|
|
218
220
|
}
|
|
219
221
|
return `\n\n${prefixPrompt}\n${result?.sourceText.text}`;
|
|
220
222
|
};
|
|
221
|
-
export const createMulmoScriptInteractively = async ({ outDirPath, cacheDirPath, filename, templateName, urls, llm, llm_model }) => {
|
|
223
|
+
export const createMulmoScriptInteractively = async ({ outDirPath, cacheDirPath, filename, templateName, urls, llm, llm_model, usageCollector, }) => {
|
|
222
224
|
mkdir(outDirPath);
|
|
223
225
|
// if urls is not empty, scrape web content and reference it in the prompt
|
|
224
|
-
const webContentPrompt = urls.length > 0 ? await scrapeWebContent(urls, cacheDirPath) : "";
|
|
226
|
+
const webContentPrompt = urls.length > 0 ? await scrapeWebContent(urls, cacheDirPath, usageCollector) : "";
|
|
225
227
|
const { agent, model, max_tokens } = llmPair(llm, llm_model);
|
|
226
228
|
GraphAILogger.log({ agent, model, max_tokens });
|
|
227
229
|
const agentFilters = [
|
|
@@ -255,6 +257,7 @@ export const createMulmoScriptInteractively = async ({ outDirPath, cacheDirPath,
|
|
|
255
257
|
}
|
|
256
258
|
}
|
|
257
259
|
});
|
|
260
|
+
graph.registerCallback(createUsageCallback(usageCollector));
|
|
258
261
|
// graph.registerCallback(cliLoadingPlugin({ nodeId: "reply", message: "Loading..." }));
|
|
259
262
|
GraphAILogger.info(`${agentHeader} Hi! What topic would you like me to generate about?\n`);
|
|
260
263
|
await graph.run();
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { UsageCollectorAPI } from "../types/usage.js";
|
|
2
|
+
export declare const deepResearch: (usageCollector?: UsageCollectorAPI) => Promise<void>;
|
|
@@ -7,6 +7,7 @@ import * as agents from "@graphai/vanilla";
|
|
|
7
7
|
import tavilySearchAgent from "../agents/tavily_agent.js";
|
|
8
8
|
import { cliLoadingPlugin } from "../utils/plugins.js";
|
|
9
9
|
import { searchQueryPrompt, reflectionPrompt, finalAnswerPrompt } from "../utils/prompt.js";
|
|
10
|
+
import { createUsageCallback } from "../utils/usage_callback.js";
|
|
10
11
|
dotenv.config({ quiet: true });
|
|
11
12
|
const vanillaAgents = agents.default ?? agents;
|
|
12
13
|
const agentHeader = "\x1b[34m● \x1b[0m\x1b[1mAgent\x1b[0m:\x1b[0m";
|
|
@@ -246,7 +247,7 @@ const graphData = {
|
|
|
246
247
|
},
|
|
247
248
|
},
|
|
248
249
|
};
|
|
249
|
-
export const deepResearch = async () => {
|
|
250
|
+
export const deepResearch = async (usageCollector) => {
|
|
250
251
|
const agentFilters = [
|
|
251
252
|
{
|
|
252
253
|
name: "consoleStreamDataAgentFilter",
|
|
@@ -260,6 +261,7 @@ export const deepResearch = async () => {
|
|
|
260
261
|
graph.registerCallback(cliLoadingPlugin({ nodeId: "reflectionAgent", message: "Analyzing search results..." }));
|
|
261
262
|
graph.registerCallback(cliLoadingPlugin({ nodeId: "tavilySearchAgent", message: "Searching..." }));
|
|
262
263
|
graph.registerCallback(cliLoadingPlugin({ nodeId: "finalAnswer", message: "Generating final answer..." }));
|
|
264
|
+
graph.registerCallback(createUsageCallback(usageCollector));
|
|
263
265
|
GraphAILogger.info(`${agentHeader} What would you like to know?\n`);
|
|
264
266
|
await graph.run();
|
|
265
267
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MulmoStoryboard, StoryToScriptGenerateMode } from "../types/index.js";
|
|
2
2
|
import type { LLM } from "../types/provider2agent.js";
|
|
3
|
-
|
|
3
|
+
import type { UsageCollectorAPI } from "../types/usage.js";
|
|
4
|
+
export declare const storyToScript: ({ story, beatsPerScene, templateName, outdir, fileName, llm, llmModel, generateMode, usageCollector, }: {
|
|
4
5
|
story: MulmoStoryboard;
|
|
5
6
|
beatsPerScene: number;
|
|
6
7
|
templateName: string;
|
|
@@ -9,4 +10,5 @@ export declare const storyToScript: ({ story, beatsPerScene, templateName, outdi
|
|
|
9
10
|
llm?: LLM;
|
|
10
11
|
llmModel?: string;
|
|
11
12
|
generateMode: StoryToScriptGenerateMode;
|
|
13
|
+
usageCollector?: UsageCollectorAPI;
|
|
12
14
|
}) => Promise<void>;
|
|
@@ -10,6 +10,7 @@ import { graphDataScriptGeneratePrompt, sceneToBeatsPrompt, storyToScriptInfoPro
|
|
|
10
10
|
import { fileWriteAgent } from "@graphai/vanilla_node_agents";
|
|
11
11
|
import validateSchemaAgent from "../agents/validate_schema_agent.js";
|
|
12
12
|
import { llmPair, settings2GraphAIConfig } from "../utils/utils.js";
|
|
13
|
+
import { createUsageCallback } from "../utils/usage_callback.js";
|
|
13
14
|
import { storyToScriptGenerateMode } from "../types/const.js";
|
|
14
15
|
import { cliLoadingPlugin } from "../utils/plugins.js";
|
|
15
16
|
const vanillaAgents = agents.default ?? agents;
|
|
@@ -248,7 +249,7 @@ const generateScriptPrompt = async (template, beatsPerScene, story) => {
|
|
|
248
249
|
const script = readScriptTemplateFile(template.scriptName);
|
|
249
250
|
return storyToScriptPrompt(script, beatsPerScene, story);
|
|
250
251
|
};
|
|
251
|
-
export const storyToScript = async ({ story, beatsPerScene, templateName, outdir, fileName, llm, llmModel, generateMode, }) => {
|
|
252
|
+
export const storyToScript = async ({ story, beatsPerScene, templateName, outdir, fileName, llm, llmModel, generateMode, usageCollector, }) => {
|
|
252
253
|
const template = readAndParseJson(getPromptTemplateFilePath(templateName), mulmoPromptTemplateSchema);
|
|
253
254
|
const { agent, model, max_tokens } = llmPair(llm, llmModel);
|
|
254
255
|
const beatsPrompt = await generateBeatsPrompt(template, beatsPerScene, story);
|
|
@@ -271,6 +272,7 @@ export const storyToScript = async ({ story, beatsPerScene, templateName, outdir
|
|
|
271
272
|
graph.injectValue("llmModel", model);
|
|
272
273
|
graph.injectValue("maxTokens", max_tokens);
|
|
273
274
|
graph.registerCallback(cliLoadingPlugin({ nodeId: "script", message: "Generating script..." }));
|
|
275
|
+
graph.registerCallback(createUsageCallback(usageCollector));
|
|
274
276
|
const result = await graph.run();
|
|
275
277
|
writingMessage(result?.writeJSON?.path ?? "");
|
|
276
278
|
};
|
package/lib/types/agent.d.ts
CHANGED
|
@@ -10,10 +10,12 @@ export type OpenAIImageOptions = {
|
|
|
10
10
|
quality?: OpenAIImageQuality;
|
|
11
11
|
background?: "opaque" | "transparent" | "auto";
|
|
12
12
|
};
|
|
13
|
+
import type { AgentUsage } from "./usage.js";
|
|
13
14
|
export type AgentBufferResult = {
|
|
14
15
|
buffer?: Buffer;
|
|
15
16
|
saved?: string;
|
|
16
17
|
text?: string;
|
|
18
|
+
usage?: AgentUsage;
|
|
17
19
|
};
|
|
18
20
|
export type AgentPromptInputs = {
|
|
19
21
|
prompt: string;
|
package/lib/types/index.d.ts
CHANGED
package/lib/types/index.js
CHANGED
package/lib/types/type.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CallbackFunction } from "graphai";
|
|
2
|
+
import type { UsageCollectorAPI } from "./usage.js";
|
|
2
3
|
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, backgroundImageSchema, backgroundImageSourceSchema, mulmoSessionStateSchema, mulmoOpenAIImageModelSchema, mulmoGoogleImageModelSchema, mulmoGoogleMovieModelSchema, mulmoReplicateMovieModelSchema, mulmoImagePromptMediaSchema, mulmoMovieMediaSchema, mulmoMoviePromptMediaSchema, markdownLayoutSchema, row2Schema, grid2x2Schema } from "./schema.js";
|
|
3
4
|
import { pdf_modes, pdf_sizes, storyToScriptGenerateMode } from "./const.js";
|
|
4
5
|
import type { LLM } from "./provider2agent.js";
|
|
@@ -68,6 +69,7 @@ export type MulmoStudioContext = {
|
|
|
68
69
|
sessionState: MulmoSessionState;
|
|
69
70
|
presentationStyle: MulmoPresentationStyle;
|
|
70
71
|
multiLingual: MulmoStudioMultiLingualArray;
|
|
72
|
+
usageCollector?: UsageCollectorAPI;
|
|
71
73
|
};
|
|
72
74
|
export type ScriptingParams = {
|
|
73
75
|
urls: string[];
|
|
@@ -78,6 +80,7 @@ export type ScriptingParams = {
|
|
|
78
80
|
llm_model?: string;
|
|
79
81
|
llm?: LLM;
|
|
80
82
|
verbose?: boolean;
|
|
83
|
+
usageCollector?: UsageCollectorAPI;
|
|
81
84
|
};
|
|
82
85
|
export type ImageProcessorParams = {
|
|
83
86
|
beat: MulmoBeat;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type AgentUsage = {
|
|
2
|
+
provider: string;
|
|
3
|
+
model: string;
|
|
4
|
+
inputTokens?: number;
|
|
5
|
+
outputTokens?: number;
|
|
6
|
+
totalTokens?: number;
|
|
7
|
+
predictSec?: number;
|
|
8
|
+
inputChars?: number;
|
|
9
|
+
};
|
|
10
|
+
export type UsageRecord = {
|
|
11
|
+
agent: string;
|
|
12
|
+
provider: string;
|
|
13
|
+
model: string;
|
|
14
|
+
beatIndex?: number;
|
|
15
|
+
inputTokens?: number;
|
|
16
|
+
outputTokens?: number;
|
|
17
|
+
totalTokens?: number;
|
|
18
|
+
predictSec?: number;
|
|
19
|
+
inputChars?: number;
|
|
20
|
+
cached: boolean;
|
|
21
|
+
retryAttempt?: number;
|
|
22
|
+
timestamp: string;
|
|
23
|
+
};
|
|
24
|
+
export type UsageCollectorAPI = {
|
|
25
|
+
add(record: Omit<UsageRecord, "timestamp"> & {
|
|
26
|
+
timestamp?: string;
|
|
27
|
+
}): void;
|
|
28
|
+
snapshot(): UsageRecord[];
|
|
29
|
+
merge(other: UsageCollectorAPI): void;
|
|
30
|
+
clear(): void;
|
|
31
|
+
readonly size: number;
|
|
32
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/utils/context.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MulmoStudioBeat, MulmoScript, MulmoPresentationStyle, MulmoStudioMultiLingual, FileObject } from "../types/type.js";
|
|
2
|
+
import { UsageCollector } from "./usage_collector.js";
|
|
2
3
|
export declare const silentMp3 = "https://github.com/receptron/mulmocast-cli/raw/refs/heads/main/assets/audio/silent300.mp3";
|
|
3
4
|
export type MulmoErrorFormatter = (error: unknown) => string | null;
|
|
4
5
|
export declare const setMulmoErrorFormatter: (formatter: MulmoErrorFormatter | null) => void;
|
|
@@ -5504,4 +5505,5 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
|
|
|
5504
5505
|
force: boolean;
|
|
5505
5506
|
backup: boolean;
|
|
5506
5507
|
lang: string;
|
|
5508
|
+
usageCollector: UsageCollector;
|
|
5507
5509
|
} | null>;
|
package/lib/utils/context.js
CHANGED
|
@@ -4,6 +4,7 @@ import { beatId, multiLingualObjectToArray } from "./utils.js";
|
|
|
4
4
|
import { mulmoStudioSchema, mulmoCaptionParamsSchema, mulmoPresentationStyleSchema } from "../types/schema.js";
|
|
5
5
|
import { MulmoPresentationStyleMethods, MulmoScriptMethods, MulmoStudioMultiLingualMethod } from "../methods/index.js";
|
|
6
6
|
import { loadMulmoConfig, mergeConfigWithScript } from "./mulmo_config.js";
|
|
7
|
+
import { UsageCollector } from "./usage_collector.js";
|
|
7
8
|
export const silentMp3 = "https://github.com/receptron/mulmocast-cli/raw/refs/heads/main/assets/audio/silent300.mp3";
|
|
8
9
|
let mulmoErrorFormatter = null;
|
|
9
10
|
export const setMulmoErrorFormatter = (formatter) => {
|
|
@@ -151,6 +152,7 @@ export const initializeContextFromFiles = async (files, raiseError, force, withB
|
|
|
151
152
|
force: Boolean(force),
|
|
152
153
|
backup: Boolean(withBackup),
|
|
153
154
|
lang: targetLang ?? studio.script.lang, // This lang is target Language. studio.lang is default Language
|
|
155
|
+
usageCollector: new UsageCollector(),
|
|
154
156
|
};
|
|
155
157
|
}
|
|
156
158
|
catch (error) {
|
package/lib/utils/filters.js
CHANGED
|
@@ -37,7 +37,7 @@ export const fileCacheAgentFilter = async (context, next) => {
|
|
|
37
37
|
const output = (await next(context)) || undefined;
|
|
38
38
|
const { buffer, text, saved } = output ?? {};
|
|
39
39
|
if (saved) {
|
|
40
|
-
return
|
|
40
|
+
return output;
|
|
41
41
|
}
|
|
42
42
|
if (buffer) {
|
|
43
43
|
writingMessage(file);
|
|
@@ -45,7 +45,7 @@ export const fileCacheAgentFilter = async (context, next) => {
|
|
|
45
45
|
if (backup) {
|
|
46
46
|
await fsPromise.writeFile(getBackupFilePath(file), buffer);
|
|
47
47
|
}
|
|
48
|
-
return
|
|
48
|
+
return output;
|
|
49
49
|
}
|
|
50
50
|
else if (text) {
|
|
51
51
|
writingMessage(file);
|
|
@@ -53,10 +53,7 @@ export const fileCacheAgentFilter = async (context, next) => {
|
|
|
53
53
|
if (backup) {
|
|
54
54
|
await fsPromise.writeFile(getBackupFilePath(file), text, "utf-8");
|
|
55
55
|
}
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
else if (saved) {
|
|
59
|
-
return true;
|
|
56
|
+
return output;
|
|
60
57
|
}
|
|
61
58
|
GraphAILogger.log("no cache, no buffer: " + file);
|
|
62
59
|
return false;
|