@gentbajko/slopify 1.2.1 → 1.4.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/README.md +15 -8
- package/dist/adapter-registry.js +14 -2
- package/dist/adapters/host-cli/index.js +192 -0
- package/dist/adapters/host-cli/transport.js +135 -0
- package/dist/adapters/image/codex.js +239 -0
- package/dist/adapters/llm/claude-code-models.js +1 -0
- package/dist/adapters/llm/claude-code.js +7 -0
- package/dist/adapters/llm/cli-login-error.js +16 -0
- package/dist/adapters/llm/codex.js +10 -0
- package/dist/catalog/validate.js +3 -1
- package/dist/edge/cli.js +36 -0
- package/dist/edge/docker.js +109 -0
- package/dist/edge/host-cli.js +58 -0
- package/dist/edge/http/actions.js +1 -1
- package/dist/edge/http/diagnostics.js +5 -1
- package/dist/edge/http/host-cli.js +247 -0
- package/dist/edge/http/providers.js +5 -1
- package/dist/host-cli/environment.js +25 -0
- package/dist/host-cli/install.js +61 -0
- package/dist/host-cli/paths.js +68 -0
- package/dist/host-cli/runtime.js +78 -0
- package/dist/host-cli/server.js +98 -0
- package/dist/host-cli/service.js +200 -0
- package/dist/host-cli/status.js +134 -0
- package/dist/kernel/db/migrations/0011-narration-prompts.sql +13 -0
- package/dist/kernel/ports/host-cli.js +95 -0
- package/dist/kernel/ports/model.js +1 -0
- package/dist/kernel/runner/attempt.js +10 -1
- package/dist/main.js +7 -1
- package/dist/slices/admission/rules.js +17 -1
- package/dist/slices/admission/schema.js +1 -0
- package/dist/slices/estimate/index.js +32 -1
- package/dist/slices/estimate/requests.js +8 -0
- package/dist/slices/library/model.js +2 -3
- package/dist/slices/library/slots.js +3 -1
- package/dist/slices/narration/preparation.js +75 -0
- package/dist/slices/narration/steering.js +89 -0
- package/dist/slices/play-drafts/convert.js +3 -0
- package/dist/slices/play-drafts/readiness.js +3 -1
- package/dist/slices/play-drafts/schema.js +1 -0
- package/dist/slices/project-templates/from-project.js +2 -0
- package/dist/slices/project-templates/setup.js +1 -0
- package/dist/slices/rebuild/admission-repo.js +2 -2
- package/dist/slices/rebuild/preview-details.js +12 -9
- package/dist/slices/rebuild/preview-plan.js +7 -1
- package/dist/slices/rebuild/recipe-audio-parts.js +89 -0
- package/dist/slices/rebuild/recipe-audio.js +52 -66
- package/dist/slices/rebuild/recipe-input-schema.js +11 -0
- package/dist/slices/rebuild/recipe-legacy.js +5 -0
- package/dist/slices/rebuild/recipe-model.js +3 -0
- package/dist/slices/rebuild/recipe-narration-text.js +33 -0
- package/dist/slices/rebuild/recipe-preparation.js +66 -0
- package/dist/slices/rebuild/recipe-provider-choice.js +7 -5
- package/dist/slices/rebuild/recipe-validation.js +9 -1
- package/dist/slices/rebuild/recipe-work.js +13 -1
- package/dist/slices/rebuild/runtime-admission.js +11 -0
- package/dist/slices/rebuild/runtime-export-inputs.js +4 -0
- package/dist/slices/rebuild/runtime-local.js +5 -0
- package/dist/slices/rebuild/runtime-materialize.js +2 -0
- package/dist/slices/rebuild/runtime-narration-reuse.js +1 -0
- package/dist/slices/rebuild/runtime-narration-text.js +75 -0
- package/dist/slices/rebuild/runtime-provider.js +30 -1
- package/dist/slices/rebuild/runtime-publication.js +13 -1
- package/dist/slices/rebuild/runtime-store.js +13 -2
- package/dist/slices/revisions/rules.js +5 -3
- package/dist/slices/settings/cli-paths.js +15 -6
- package/dist/slices/settings/cli-status.js +3 -1
- package/dist/slices/settings/model.js +9 -0
- package/dist/slices/settings/readiness.js +24 -3
- package/dist/slices/storage/asset-name.js +3 -0
- package/dist/slices/storage/layout.js +4 -0
- package/dist/slices/storage/model.js +2 -0
- package/dist/slices/storage/schema.js +1 -0
- package/dist/web/assets/index-DV1LKwBP.js +137 -0
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/scripts/docker-run.sh +8 -45
- package/dist/web/assets/index-Cq2O1JRf.js +0 -137
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { plainText } from "../article/plain.js";
|
|
2
2
|
import { splitEndMatter } from "../article/split.js";
|
|
3
3
|
import { observeNarration } from "../narration/live.js";
|
|
4
|
+
import { validatePreparation } from "../narration/preparation.js";
|
|
5
|
+
import { prepareRequests } from "../narration/steering.js";
|
|
4
6
|
import { chaptersFrom } from "../research/planner.js";
|
|
5
7
|
import { sourcedAnswer } from "../research/synthesis.js";
|
|
6
8
|
import { discardPreparedAssets, writeAsset } from "../storage/assets.js";
|
|
@@ -28,7 +30,7 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
|
|
|
28
30
|
messages: input.messages,
|
|
29
31
|
webSearch: input.webSearch,
|
|
30
32
|
previewLabel: piece.key,
|
|
31
|
-
check: (value) => checkAnswer(piece, value),
|
|
33
|
+
check: (value) => checkAnswer(piece, value) ?? checkPreparation(deps, context, piece, value),
|
|
32
34
|
});
|
|
33
35
|
if (!answer.ok)
|
|
34
36
|
return "held";
|
|
@@ -63,6 +65,7 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
|
|
|
63
65
|
: await probeDurationMs(deps.ffmpeg, outputPath(deps.paths, context.work.projectId, asset.path), context.signal, deps.log);
|
|
64
66
|
await publishResult(deps, context, piece, [], {
|
|
65
67
|
text: input.text,
|
|
68
|
+
...(input.spokenText === undefined ? {} : { spokenText: input.spokenText }),
|
|
66
69
|
logicalKey: input.logicalKey,
|
|
67
70
|
logicalText: input.logicalText,
|
|
68
71
|
segment: input.segment,
|
|
@@ -125,6 +128,18 @@ function checkAnswer(piece, answer) {
|
|
|
125
128
|
return undefined;
|
|
126
129
|
}
|
|
127
130
|
async function publishText(deps, context, piece, answer) {
|
|
131
|
+
if (piece.input.kind === "llm" && piece.input.preparation !== undefined) {
|
|
132
|
+
const checked = validatePreparation(answer.text, piece.input.preparation.source);
|
|
133
|
+
if (!checked.ok)
|
|
134
|
+
throw new Error(checked.reason);
|
|
135
|
+
await publishResult(deps, context, piece, [], {
|
|
136
|
+
text: answer.text,
|
|
137
|
+
preparation: piece.input.preparation,
|
|
138
|
+
cues: checked.cues,
|
|
139
|
+
logicalFingerprint: piece.logicalFingerprint ?? piece.fingerprint,
|
|
140
|
+
});
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
128
143
|
if (piece.key === "article:body") {
|
|
129
144
|
const parts = splitEndMatter(answer.text);
|
|
130
145
|
await publishResult(deps, context, piece, preparedTexts(deps, context, piece, [
|
|
@@ -182,3 +197,17 @@ function narrationLabel(deps, context, piece) {
|
|
|
182
197
|
const index = parts.findIndex((recipe) => recipe.key === piece.key);
|
|
183
198
|
return parts.length > 1 && index >= 0 ? `${name} part ${index + 1} of ${parts.length}` : name;
|
|
184
199
|
}
|
|
200
|
+
function checkPreparation(deps, context, piece, answer) {
|
|
201
|
+
if (piece.input.kind !== "llm" || piece.input.preparation === undefined)
|
|
202
|
+
return undefined;
|
|
203
|
+
const source = piece.input.preparation.source;
|
|
204
|
+
const checked = validatePreparation(answer.text, source);
|
|
205
|
+
if (!checked.ok)
|
|
206
|
+
return checked.reason;
|
|
207
|
+
const row = deps.db
|
|
208
|
+
.prepare("SELECT recipe_context FROM revision_work WHERE id=?")
|
|
209
|
+
.get(context.work.workId);
|
|
210
|
+
const model = savedCatalogue(row?.recipe_context).tts.find((model) => model.provider === "inworld" && model.id === "inworld-tts-2");
|
|
211
|
+
const prepared = prepareRequests(source, checked.cues, model?.tts.maxCharacters ?? 4000);
|
|
212
|
+
return prepared.ok ? undefined : prepared.reason;
|
|
213
|
+
}
|
|
@@ -80,6 +80,8 @@ export async function publishResult(deps, context, piece, outputs, payload, asse
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
function pieceKind(piece) {
|
|
83
|
+
if (piece.input.kind === "llm" && piece.input.preparation !== undefined)
|
|
84
|
+
return "prompt_written";
|
|
83
85
|
if (piece.input.kind === "tts")
|
|
84
86
|
return piece.input.segment === "body" ? "chunk" : "segment";
|
|
85
87
|
if (piece.input.kind === "provided" && /^audio:body:.+:\d+$/.test(piece.key))
|
|
@@ -97,6 +99,10 @@ function pieceKind(piece) {
|
|
|
97
99
|
return "article_written";
|
|
98
100
|
}
|
|
99
101
|
function pieceIndex(deps, context, piece) {
|
|
102
|
+
if (piece.input.kind === "llm" && piece.input.preparation?.segment === "intro")
|
|
103
|
+
return 1;
|
|
104
|
+
if (piece.input.kind === "llm" && piece.input.preparation?.segment === "outro")
|
|
105
|
+
return 2;
|
|
100
106
|
// Intro and outro can resolve independently. Reserve alternating ordinals so a
|
|
101
107
|
// late intro part cannot collide with an outro that was already published.
|
|
102
108
|
const segment = /^audio:(intro|outro):(\d+)$/.exec(piece.key);
|
|
@@ -116,6 +122,9 @@ function pieceIndex(deps, context, piece) {
|
|
|
116
122
|
"subtitles:files",
|
|
117
123
|
"export:wav",
|
|
118
124
|
"export:video",
|
|
125
|
+
"narration:files:body",
|
|
126
|
+
"narration:files:intro",
|
|
127
|
+
"narration:files:outro",
|
|
119
128
|
];
|
|
120
129
|
const localIndex = localOrder.indexOf(piece.key);
|
|
121
130
|
if (localIndex >= 0)
|
|
@@ -126,10 +135,13 @@ function pieceIndex(deps, context, piece) {
|
|
|
126
135
|
.get(context.work.workId);
|
|
127
136
|
if (view !== undefined && row?.recipe_context !== null && row?.recipe_context !== undefined) {
|
|
128
137
|
const keys = executionPlan(deps, view, savedCatalogue(row.recipe_context)).recipes.filter((recipe) => recipe.stage === context.work.kind &&
|
|
138
|
+
(piece.input.kind !== "llm" ||
|
|
139
|
+
piece.input.preparation?.segment !== "body" ||
|
|
140
|
+
(recipe.input.kind === "llm" && recipe.input.preparation?.segment === "body")) &&
|
|
129
141
|
pieceKind({ ...piece, key: recipe.key, input: recipe.input }) === pieceKind(piece));
|
|
130
142
|
const index = keys.findIndex((recipe) => recipe.key === piece.key);
|
|
131
143
|
if (index >= 0)
|
|
132
|
-
return index + 1;
|
|
144
|
+
return (index + (piece.input.kind === "llm" && piece.input.preparation?.segment === "body" ? 3 : 1));
|
|
133
145
|
}
|
|
134
146
|
const last = piece.key.split(":").at(-1);
|
|
135
147
|
return last !== undefined && /^\d+$/.test(last) ? Number(last) : 1;
|
|
@@ -26,6 +26,7 @@ export function executionStages(deps, projectId) {
|
|
|
26
26
|
.prepare("SELECT work_key FROM revision_work_reservations WHERE revision_id=?")
|
|
27
27
|
.all(head)
|
|
28
28
|
.map((row) => String(row.work_key)));
|
|
29
|
+
const futureKeys = new Map();
|
|
29
30
|
return rows
|
|
30
31
|
.filter((row) => workPieces(deps.db, String(row.id)).some((piece) => {
|
|
31
32
|
if (!keys.has(piece.key))
|
|
@@ -33,8 +34,18 @@ export function executionStages(deps, projectId) {
|
|
|
33
34
|
if (piece.key === "subtitles:timing" && view?.revision.content.subtitleCues !== undefined)
|
|
34
35
|
return false;
|
|
35
36
|
if (piece.input.kind === "deferred" && piece.key.endsWith(":future")) {
|
|
36
|
-
const
|
|
37
|
-
|
|
37
|
+
const cacheKey = String(row.id);
|
|
38
|
+
let deferred = futureKeys.get(cacheKey);
|
|
39
|
+
if (deferred === undefined) {
|
|
40
|
+
const origin = executionView(deps, projectId, String(row.revision_id));
|
|
41
|
+
if (origin === undefined || row.recipe_context === null)
|
|
42
|
+
return true;
|
|
43
|
+
deferred = new Set(executionPlan(deps, origin, savedCatalogue(row.recipe_context))
|
|
44
|
+
.recipes.filter((recipe) => recipe.deferred)
|
|
45
|
+
.map((recipe) => recipe.key));
|
|
46
|
+
futureKeys.set(cacheKey, deferred);
|
|
47
|
+
}
|
|
48
|
+
return deferred.has(piece.key);
|
|
38
49
|
}
|
|
39
50
|
return true;
|
|
40
51
|
}))
|
|
@@ -79,9 +79,11 @@ export function validateNarrationIntent(base, edit) {
|
|
|
79
79
|
});
|
|
80
80
|
const keys = new Set(recipes.flatMap((row) => row.input.kind === "tts"
|
|
81
81
|
? [row.input.logicalKey]
|
|
82
|
-
: row.
|
|
83
|
-
? [row.
|
|
84
|
-
:
|
|
82
|
+
: row.input.kind === "llm" && row.input.preparation !== undefined
|
|
83
|
+
? [row.input.preparation.logicalKey]
|
|
84
|
+
: row.stage === "audio" && row.key.endsWith(":1")
|
|
85
|
+
? [row.key.slice(0, -2)]
|
|
86
|
+
: []));
|
|
85
87
|
const fields = [];
|
|
86
88
|
for (const [key, value] of Object.entries(edit.content.narrationOverrides))
|
|
87
89
|
if (!isDeepStrictEqual(value, base.revision.content.narrationOverrides[key]) && !keys.has(key))
|
|
@@ -13,11 +13,14 @@ const pathSchema = z
|
|
|
13
13
|
.refine((value) => value === "" || (isAbsolute(value) && !/[\p{Cc}]/u.test(value)));
|
|
14
14
|
const storedPathSchema = z.string().max(cliPathMaxLength).nullable();
|
|
15
15
|
const saves = new WeakMap();
|
|
16
|
+
function installationId(id) {
|
|
17
|
+
return id === "codex-image" ? "codex" : id;
|
|
18
|
+
}
|
|
16
19
|
export function cliPathStatus(db, id) {
|
|
17
20
|
const provider = providerById(id);
|
|
18
21
|
if (provider.auth !== "cli")
|
|
19
22
|
throw new Error(`${id} is not a CLI provider`);
|
|
20
|
-
const stored = readSetting(db, `cli.path.${id}`);
|
|
23
|
+
const stored = readSetting(db, `cli.path.${installationId(id)}`);
|
|
21
24
|
const configured = stored === undefined ? null : storedPathSchema.parse(JSON.parse(stored));
|
|
22
25
|
return { configured, command: configured ?? provider.binary };
|
|
23
26
|
}
|
|
@@ -26,22 +29,28 @@ export function cliBinary(db, id) {
|
|
|
26
29
|
return cliPathStatus(db, id).command;
|
|
27
30
|
}
|
|
28
31
|
export async function saveCliPath(deps, id, raw) {
|
|
32
|
+
if (deps.hostCliStatus)
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
message: "CLI paths are managed on the host. Rerun the Docker launcher after changing installations.",
|
|
36
|
+
};
|
|
29
37
|
// A slow version probe must not overwrite a later Save or Reset from another tab.
|
|
30
38
|
let queue = saves.get(deps.db);
|
|
31
39
|
if (queue === undefined) {
|
|
32
40
|
queue = new Map();
|
|
33
41
|
saves.set(deps.db, queue);
|
|
34
42
|
}
|
|
35
|
-
const
|
|
43
|
+
const sharedId = installationId(id);
|
|
44
|
+
const previous = queue.get(sharedId) ?? Promise.resolve();
|
|
36
45
|
const next = previous.then(() => save(deps, id, raw));
|
|
37
46
|
const settled = next.then(() => { }, () => { });
|
|
38
|
-
queue.set(
|
|
47
|
+
queue.set(sharedId, settled);
|
|
39
48
|
try {
|
|
40
49
|
return await next;
|
|
41
50
|
}
|
|
42
51
|
finally {
|
|
43
|
-
if (queue.get(
|
|
44
|
-
queue.delete(
|
|
52
|
+
if (queue.get(sharedId) === settled)
|
|
53
|
+
queue.delete(sharedId);
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
56
|
async function save(deps, id, raw) {
|
|
@@ -84,7 +93,7 @@ async function save(deps, id, raw) {
|
|
|
84
93
|
message: probed.error ??
|
|
85
94
|
`${provider.displayName} did not answer --version successfully within ${cliProbeTimeoutMs / 1000} seconds. Choose its executable file without arguments.`,
|
|
86
95
|
};
|
|
87
|
-
writeSetting(deps.db, `cli.path.${id}`, JSON.stringify(configured));
|
|
96
|
+
writeSetting(deps.db, `cli.path.${installationId(id)}`, JSON.stringify(configured));
|
|
88
97
|
return {
|
|
89
98
|
ok: true,
|
|
90
99
|
status: {
|
|
@@ -35,7 +35,7 @@ export function readinessFromProbe(result, provider) {
|
|
|
35
35
|
return { kind: "cli", installed: false };
|
|
36
36
|
}
|
|
37
37
|
const version = versionFrom(result.stdout);
|
|
38
|
-
if (provider.id !== "codex")
|
|
38
|
+
if (provider.id !== "codex" && provider.id !== "codex-image")
|
|
39
39
|
return version === undefined
|
|
40
40
|
? { kind: "cli", installed: true }
|
|
41
41
|
: { kind: "cli", installed: true, version };
|
|
@@ -43,6 +43,7 @@ export function readinessFromProbe(result, provider) {
|
|
|
43
43
|
return {
|
|
44
44
|
kind: "cli",
|
|
45
45
|
installed: true,
|
|
46
|
+
issueKind: "version",
|
|
46
47
|
issue: `Slopify could not verify this Codex CLI version. Install Codex CLI ${minimumCodexCliVersion} or newer, then try again.`,
|
|
47
48
|
};
|
|
48
49
|
if (!versionAtLeast(version, minimumCodexCliVersion))
|
|
@@ -50,6 +51,7 @@ export function readinessFromProbe(result, provider) {
|
|
|
50
51
|
kind: "cli",
|
|
51
52
|
installed: true,
|
|
52
53
|
version,
|
|
54
|
+
issueKind: "version",
|
|
53
55
|
issue: `Codex CLI ${minimumCodexCliVersion} or newer is required; version ${version} is installed. Update Codex CLI and try again.`,
|
|
54
56
|
};
|
|
55
57
|
return { kind: "cli", installed: true, version };
|
|
@@ -17,6 +17,7 @@ export const providerIds = [
|
|
|
17
17
|
"replicate",
|
|
18
18
|
"openai-image",
|
|
19
19
|
"google-image",
|
|
20
|
+
"codex-image",
|
|
20
21
|
];
|
|
21
22
|
export function isLocalCliProvider(id) {
|
|
22
23
|
return id === "claude-code" || id === "codex" || id === "gemini" || id === "codex-image";
|
|
@@ -55,6 +56,14 @@ export const providers = [
|
|
|
55
56
|
{ id: "replicate", family: "image", displayName: "Replicate", auth: "key" },
|
|
56
57
|
{ id: "openai-image", family: "image", displayName: "OpenAI", auth: "key" },
|
|
57
58
|
{ id: "google-image", family: "image", displayName: "Google", auth: "key" },
|
|
59
|
+
{
|
|
60
|
+
id: "codex-image",
|
|
61
|
+
family: "image",
|
|
62
|
+
displayName: "Codex CLI",
|
|
63
|
+
auth: "cli",
|
|
64
|
+
binary: "codex",
|
|
65
|
+
versionArgs: ["--version"],
|
|
66
|
+
},
|
|
58
67
|
];
|
|
59
68
|
export function providerById(id) {
|
|
60
69
|
const found = providers.find((provider) => provider.id === id);
|
|
@@ -1,21 +1,42 @@
|
|
|
1
1
|
import { cliPathStatus } from "./cli-paths.js";
|
|
2
|
-
import {
|
|
3
|
-
import { providers } from "./model.js";
|
|
2
|
+
import { cliProbeTimeoutMs, readinessFromProbe } from "./cli-status.js";
|
|
3
|
+
import { isLocalCliProvider, providers } from "./model.js";
|
|
4
4
|
import { keyedProviders } from "./repo.js";
|
|
5
5
|
// Every supported provider is listed, keyed or not, found or not, so Play can grey one out with
|
|
6
6
|
// a reason instead of hiding it. A keyed provider is ready when a key is stored; a CLI provider
|
|
7
7
|
// when its binary answers. Readiness is computed per request using the latest saved executable override.
|
|
8
8
|
export async function providerStatuses(deps) {
|
|
9
9
|
const keyed = keyedProviders(deps.db);
|
|
10
|
+
const probes = new Map();
|
|
10
11
|
return await Promise.all(providers.map(async (provider) => {
|
|
11
12
|
const base = { id: provider.id, family: provider.family, displayName: provider.displayName };
|
|
12
13
|
if (provider.auth !== "cli")
|
|
13
14
|
return { ...base, readiness: keyedReadiness(keyed, provider.id) };
|
|
15
|
+
if (deps.hostCliStatus && isLocalCliProvider(provider.id)) {
|
|
16
|
+
const status = await deps.hostCliStatus(provider.id);
|
|
17
|
+
return {
|
|
18
|
+
...base,
|
|
19
|
+
cliPath: { configured: null, command: status.command, managedOnHost: true },
|
|
20
|
+
readiness: {
|
|
21
|
+
kind: "cli",
|
|
22
|
+
installed: status.installed,
|
|
23
|
+
...(status.version === undefined ? {} : { version: status.version }),
|
|
24
|
+
...(status.issueKind === undefined ? {} : { issueKind: status.issueKind }),
|
|
25
|
+
...(status.issue === undefined ? {} : { issue: status.issue }),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
14
29
|
const cliPath = cliPathStatus(deps.db, provider.id);
|
|
30
|
+
const probeKey = JSON.stringify([cliPath.command, provider.versionArgs]);
|
|
31
|
+
let result = probes.get(probeKey);
|
|
32
|
+
if (result === undefined) {
|
|
33
|
+
result = deps.probe(cliPath.command, provider.versionArgs, cliProbeTimeoutMs);
|
|
34
|
+
probes.set(probeKey, result);
|
|
35
|
+
}
|
|
15
36
|
return {
|
|
16
37
|
...base,
|
|
17
38
|
cliPath,
|
|
18
|
-
readiness: await
|
|
39
|
+
readiness: readinessFromProbe(await result, provider),
|
|
19
40
|
};
|
|
20
41
|
}));
|
|
21
42
|
}
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
// research and the article each store what they sent.
|
|
8
8
|
export function assetOf(output) {
|
|
9
9
|
const asset = output.role.replaceAll("_", "-");
|
|
10
|
+
if ((output.role === "narration_txt" || output.role === "tts_script") &&
|
|
11
|
+
output.meta.segment !== undefined)
|
|
12
|
+
return `${asset}-${output.meta.segment}`;
|
|
10
13
|
if (output.role === "instructions") {
|
|
11
14
|
return `${output.stageKind}-${asset}`;
|
|
12
15
|
}
|
|
@@ -30,6 +30,10 @@ export function outputFileName(role, index, extension, stageKind) {
|
|
|
30
30
|
return "article.md";
|
|
31
31
|
case "article_txt":
|
|
32
32
|
return "article.txt";
|
|
33
|
+
case "narration_txt":
|
|
34
|
+
return "narration.txt";
|
|
35
|
+
case "tts_script":
|
|
36
|
+
return "tts-script.txt";
|
|
33
37
|
case "sources":
|
|
34
38
|
return "sources.txt";
|
|
35
39
|
case "glossary":
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { outputRoles, stagedFileStates, stageKinds } from "./model.js";
|
|
3
3
|
export const metaSchema = z.object({
|
|
4
|
+
segment: z.enum(["body", "intro", "outro"]).optional(),
|
|
4
5
|
subtitleOmissions: z
|
|
5
6
|
.array(z.object({ start: z.number().finite().nonnegative(), text: z.string() }))
|
|
6
7
|
.optional(),
|