@gentbajko/slopify 2.0.1 → 2.0.3
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/dist/adapters/alignment/cache.js +17 -1
- package/dist/adapters/alignment/prefetch.js +35 -0
- package/dist/adapters/alignment/protocol.js +11 -1
- package/dist/adapters/alignment/runner.js +4 -1
- package/dist/adapters/alignment/worker.js +28 -5
- package/dist/adapters/explain.js +93 -0
- package/dist/adapters/ffmpeg.js +3 -3
- package/dist/adapters/host-cli/index.js +9 -2
- package/dist/adapters/host-cli/transport.js +4 -4
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +12 -12
- package/dist/adapters/image/codex.js +24 -8
- package/dist/adapters/image/fal.js +12 -6
- package/dist/adapters/image/google.js +18 -8
- package/dist/adapters/image/models.js +8 -2
- package/dist/adapters/image/openai.js +15 -6
- package/dist/adapters/image/replicate.js +16 -7
- package/dist/adapters/llm/claude-code-models.js +1 -1
- package/dist/adapters/llm/claude-code.js +16 -7
- package/dist/adapters/llm/cli-login-error.js +2 -1
- package/dist/adapters/llm/codex-models.js +1 -1
- package/dist/adapters/llm/codex.js +5 -6
- package/dist/adapters/llm/document-workspace.js +2 -2
- package/dist/adapters/llm/gemini-models.js +1 -1
- package/dist/adapters/llm/gemini-workspace.js +1 -1
- package/dist/adapters/llm/gemini.js +15 -11
- package/dist/adapters/llm/openrouter.js +21 -9
- package/dist/adapters/llm/run-cli.js +14 -11
- package/dist/adapters/tts/cartesia.js +10 -3
- package/dist/adapters/tts/elevenlabs.js +18 -6
- package/dist/adapters/tts/inworld-async.js +47 -16
- package/dist/adapters/tts/inworld.js +25 -15
- package/dist/adapters/tts/openai.js +18 -6
- package/dist/catalog/registry.js +13 -10
- package/dist/catalog/store.js +1 -1
- package/dist/catalog/validate.js +13 -4
- package/dist/edge/cli.js +37 -8
- package/dist/edge/docker-launch.js +6 -6
- package/dist/edge/docker-projects/engine.js +34 -13
- package/dist/edge/docker-projects/install.js +8 -8
- package/dist/edge/docker-projects/state.js +6 -6
- package/dist/edge/docker-projects/tree.js +6 -6
- package/dist/edge/docker.js +13 -13
- package/dist/edge/http/actions.js +27 -25
- package/dist/edge/http/app.js +3 -3
- package/dist/edge/http/audio-preview.js +1 -1
- package/dist/edge/http/checkpoints.js +5 -1
- package/dist/edge/http/diagnostics.js +3 -1
- package/dist/edge/http/draft-files.js +2 -2
- package/dist/edge/http/draft-problem.js +11 -1
- package/dist/edge/http/files.js +4 -4
- package/dist/edge/http/folder-location.js +3 -3
- package/dist/edge/http/fonts.js +22 -10
- package/dist/edge/http/host-cli.js +34 -12
- package/dist/edge/http/multipart.js +7 -5
- package/dist/edge/http/open-folder.js +2 -2
- package/dist/edge/http/planning.js +4 -4
- package/dist/edge/http/problem.js +2 -2
- package/dist/edge/http/project-templates.js +7 -3
- package/dist/edge/http/projects.js +8 -8
- package/dist/edge/http/prompts.js +9 -4
- package/dist/edge/http/providers.js +15 -7
- package/dist/edge/http/revision-files.js +3 -3
- package/dist/edge/http/revisions.js +30 -4
- package/dist/edge/http/schedules.js +9 -9
- package/dist/edge/http/settings.js +15 -9
- package/dist/edge/http/staging.js +5 -5
- package/dist/edge/http/storage.js +4 -4
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/tutorial.js +2 -2
- package/dist/edge/http/update.js +3 -3
- package/dist/host-cli/install.js +28 -6
- package/dist/host-cli/paths.js +6 -6
- package/dist/host-cli/server.js +1 -1
- package/dist/host-cli/service.js +11 -11
- package/dist/host-cli/status.js +7 -3
- package/dist/kernel/config/index.js +6 -4
- package/dist/kernel/db/migrate.js +2 -1
- package/dist/kernel/db/migrations/0013-schedule-topic-queue.sql +4 -0
- package/dist/kernel/errors.js +12 -0
- package/dist/kernel/lock.js +4 -3
- package/dist/kernel/paths.js +5 -0
- package/dist/kernel/ports/subtitles.js +15 -1
- package/dist/kernel/runner/attempt.js +26 -2
- package/dist/kernel/runner/index.js +16 -6
- package/dist/kernel/runner/progress.js +56 -0
- package/dist/main.js +20 -4
- package/dist/slices/admission/rules.js +32 -20
- package/dist/slices/article/continuation.js +4 -2
- package/dist/slices/article/plain.js +2 -2
- package/dist/slices/article/provided-entries.js +3 -3
- package/dist/slices/article/run.js +2 -2
- package/dist/slices/article/segments.js +5 -3
- package/dist/slices/control/providers.js +20 -7
- package/dist/slices/fonts/catalog.js +1 -1
- package/dist/slices/fonts/upload.js +1 -1
- package/dist/slices/images/run.js +5 -5
- package/dist/slices/library/lint.js +13 -7
- package/dist/slices/library/slots.js +2 -2
- package/dist/slices/narration/concat.js +1 -1
- package/dist/slices/narration/pronunciation-chunks.js +3 -3
- package/dist/slices/narration/pronunciation.js +2 -2
- package/dist/slices/narration/run.js +7 -7
- package/dist/slices/narration/steering.js +3 -3
- package/dist/slices/play-drafts/convert.js +5 -4
- package/dist/slices/play-drafts/readiness.js +12 -6
- package/dist/slices/play-drafts/review-inputs.js +7 -4
- package/dist/slices/play-drafts/review.js +3 -3
- package/dist/slices/play-drafts/service.js +3 -1
- package/dist/slices/play-drafts/uploads.js +1 -1
- package/dist/slices/rebuild/admission-repo.js +2 -2
- package/dist/slices/rebuild/recipe-legacy.js +1 -1
- package/dist/slices/rebuild/recipe-narration-text.js +1 -1
- package/dist/slices/rebuild/recipe-visual.js +1 -1
- package/dist/slices/rebuild/recovery.js +2 -2
- package/dist/slices/rebuild/repo.js +3 -3
- package/dist/slices/rebuild/runtime-admission.js +4 -4
- package/dist/slices/rebuild/runtime-article.js +5 -5
- package/dist/slices/rebuild/runtime-checkpoints.js +4 -4
- package/dist/slices/rebuild/runtime-export-inputs.js +6 -6
- package/dist/slices/rebuild/runtime-export.js +4 -4
- package/dist/slices/rebuild/runtime-local.js +6 -6
- package/dist/slices/rebuild/runtime-narration-text.js +46 -7
- package/dist/slices/rebuild/runtime-piece-label.js +62 -0
- package/dist/slices/rebuild/runtime-provider.js +6 -4
- package/dist/slices/rebuild/runtime-publication.js +3 -3
- package/dist/slices/rebuild/runtime-run.js +11 -1
- package/dist/slices/rebuild/runtime-store.js +45 -15
- package/dist/slices/rebuild/runtime-subtitles.js +80 -17
- package/dist/slices/rebuild/transition-repo.js +6 -6
- package/dist/slices/reruns/index.js +1 -1
- package/dist/slices/research/run.js +9 -7
- package/dist/slices/research/synthesis.js +4 -2
- package/dist/slices/revisions/mutation-assets.js +26 -8
- package/dist/slices/revisions/mutation-cues.js +1 -1
- package/dist/slices/revisions/rules.js +18 -9
- package/dist/slices/revisions/schema.js +1 -1
- package/dist/slices/schedules/repo.js +55 -11
- package/dist/slices/schedules/scheduler.js +25 -8
- package/dist/slices/schedules/schema.js +19 -4
- package/dist/slices/schedules/service.js +2 -0
- package/dist/slices/settings/cli-paths.js +7 -4
- package/dist/slices/settings/models.js +4 -4
- package/dist/slices/settings/playback.js +2 -2
- package/dist/slices/storage/assets.js +5 -5
- package/dist/slices/storage/layout.js +1 -1
- package/dist/slices/subtitles/captions.js +1 -1
- package/dist/slices/subtitles/prepare.js +7 -6
- package/dist/slices/subtitles/transcript.js +1 -1
- package/dist/slices/thumbnail/run.js +8 -8
- package/dist/slices/video/audio-inputs.js +2 -2
- package/dist/slices/video/ffmpeg.js +6 -4
- package/dist/slices/video/plan.js +1 -1
- package/dist/slices/video/run.js +2 -2
- package/dist/updater/plan.js +1 -1
- package/dist/updater/service.js +4 -3
- package/dist/updater/worker.js +6 -4
- package/dist/web/assets/{index-CZNiHm8P.css → index-Bwtmu9ei.css} +1 -1
- package/dist/web/assets/index-DjhMJqMN.js +141 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-DPgGLX5m.js +0 -137
|
@@ -22,19 +22,19 @@ export async function runImages(deps, context, providers) {
|
|
|
22
22
|
const { projectId } = context.stage;
|
|
23
23
|
const project = projectById(deps.db, projectId);
|
|
24
24
|
if (project === undefined) {
|
|
25
|
-
throw new Error(
|
|
25
|
+
throw new Error("This project no longer exists; it may have been deleted while it was running.");
|
|
26
26
|
}
|
|
27
27
|
const choice = project.config.images;
|
|
28
28
|
if (choice === undefined) {
|
|
29
29
|
// Admission refuses a run whose images are Generate without a provider and a model, so
|
|
30
30
|
// reaching here is a bug in admission rather than the user's.
|
|
31
|
-
throw new Error("
|
|
31
|
+
throw new Error("No image model is set for this project. Choose one in Edit project → Providers, then Retry stage.");
|
|
32
32
|
}
|
|
33
33
|
const pieces = plan(deps, context, project.config);
|
|
34
34
|
if (pieces.length === 0) {
|
|
35
35
|
// Admission makes an image source mandatory and puts the Number at one or more, so an
|
|
36
36
|
// empty plan is a bug upstream rather than a run with no pictures.
|
|
37
|
-
throw new Error("
|
|
37
|
+
throw new Error("Slopify hit an internal error (no image prompt was selected). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
38
38
|
}
|
|
39
39
|
// The run's own frame. The adapter turns it into whatever its provider spells the
|
|
40
40
|
// closest supported size, and the render crops whatever is left over.
|
|
@@ -78,7 +78,7 @@ function plan(deps, context, config) {
|
|
|
78
78
|
// it, so the run carries the substituted text under this key.
|
|
79
79
|
const prompt = config.rendered[`imagePrompts.${String(at)}`];
|
|
80
80
|
if (prompt === undefined) {
|
|
81
|
-
throw new Error(`
|
|
81
|
+
throw new Error(`Slopify hit an internal error (the image prompt ${picked.name} was never filled in). Retry stage; if it happens again, use Download diagnostics in Settings and report it.`);
|
|
82
82
|
}
|
|
83
83
|
for (let send = 1; send <= picked.number; send += 1) {
|
|
84
84
|
planned.push({
|
|
@@ -224,7 +224,7 @@ function report(deps, context, done, total) {
|
|
|
224
224
|
}
|
|
225
225
|
function payloadOf(piece) {
|
|
226
226
|
if (piece.payload === null) {
|
|
227
|
-
throw new Error(
|
|
227
|
+
throw new Error("Slopify hit an internal error (a saved image step is empty). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
228
228
|
}
|
|
229
229
|
return imagePayload.parse(JSON.parse(piece.payload));
|
|
230
230
|
}
|
|
@@ -13,17 +13,23 @@ function lint(name, body) {
|
|
|
13
13
|
const fields = [];
|
|
14
14
|
const trimmed = name.trim();
|
|
15
15
|
if (trimmed === "") {
|
|
16
|
-
fields.push({ field: "name", message: "
|
|
16
|
+
fields.push({ field: "name", message: "Enter a name." });
|
|
17
17
|
}
|
|
18
18
|
else if (trimmed.length > nameMax) {
|
|
19
|
-
fields.push({
|
|
19
|
+
fields.push({
|
|
20
|
+
field: "name",
|
|
21
|
+
message: `Keep the name to ${String(nameMax)} characters or fewer.`,
|
|
22
|
+
});
|
|
20
23
|
}
|
|
21
24
|
if (body.trim() === "") {
|
|
22
|
-
fields.push({ field: "body", message: "
|
|
25
|
+
fields.push({ field: "body", message: "Enter the text." });
|
|
23
26
|
return fields;
|
|
24
27
|
}
|
|
25
28
|
if (body.length > bodyMax) {
|
|
26
|
-
fields.push({
|
|
29
|
+
fields.push({
|
|
30
|
+
field: "body",
|
|
31
|
+
message: `The text is too long. Keep it to ${String(bodyMax)} characters or fewer.`,
|
|
32
|
+
});
|
|
27
33
|
return fields;
|
|
28
34
|
}
|
|
29
35
|
for (const error of detectSlots(body).errors) {
|
|
@@ -37,11 +43,11 @@ function describe(error, body) {
|
|
|
37
43
|
const where = positionOf(body, error.at);
|
|
38
44
|
switch (error.kind) {
|
|
39
45
|
case "unclosed":
|
|
40
|
-
return `The \`{{\` at ${where} is never closed.`;
|
|
46
|
+
return `The \`{{\` at ${where} is never closed. Add \`}}\` after the keyword name.`;
|
|
41
47
|
case "empty":
|
|
42
|
-
return `The
|
|
48
|
+
return `The keyword at ${where} has no name. Write a name between \`{{\` and \`}}\`.`;
|
|
43
49
|
case "nested":
|
|
44
|
-
return `The
|
|
50
|
+
return `The keyword at ${where} contains a brace. Keywords cannot be placed inside other keywords.`;
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
53
|
function positionOf(body, at) {
|
|
@@ -58,7 +58,7 @@ function body(db, kind, name, field, missing, into, key, snapshot) {
|
|
|
58
58
|
}
|
|
59
59
|
const prompt = snapshotPrompt(db, snapshot, kind, name);
|
|
60
60
|
if (prompt === undefined) {
|
|
61
|
-
missing.push({ field, message: `That ${kind} prompt
|
|
61
|
+
missing.push({ field, message: `That ${kind} prompt was deleted. Choose another.` });
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
into.push({ key, body: prompt.body });
|
|
@@ -71,7 +71,7 @@ function pickEntry(db, category, choice, missing, snapshot) {
|
|
|
71
71
|
if (entry === undefined) {
|
|
72
72
|
missing.push({
|
|
73
73
|
field: category,
|
|
74
|
-
message: `That ${category} entry
|
|
74
|
+
message: `That ${category} entry was deleted. Choose another.`,
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
return entry;
|
|
@@ -46,7 +46,7 @@ export async function joinNarration(deps, input) {
|
|
|
46
46
|
const first = input.files[0];
|
|
47
47
|
if (first === undefined) {
|
|
48
48
|
// The stage refuses an empty narration before it gets here, so this is a bug.
|
|
49
|
-
throw new Error("there
|
|
49
|
+
throw new Error("Slopify hit an internal error (there is no narration audio to join). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
50
50
|
}
|
|
51
51
|
mkdirSync(dirname(input.output), { recursive: true, mode: 0o700 });
|
|
52
52
|
if (input.files.length === 1) {
|
|
@@ -18,7 +18,7 @@ export function pronunciationChunks(source, chunking, entries, overriddenKeys =
|
|
|
18
18
|
const chunks = chunkNarration(source, chunking).map((text) => {
|
|
19
19
|
const start = source.indexOf(text, offset);
|
|
20
20
|
if (start < 0)
|
|
21
|
-
throw new Error("
|
|
21
|
+
throw new Error("Slopify hit an internal error (a narration chunk isn't in the article text). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
22
22
|
offset = start + text.length;
|
|
23
23
|
return { key: nextKey(text), text, start, end: offset };
|
|
24
24
|
});
|
|
@@ -78,7 +78,7 @@ export function pronunciationChunks(source, chunking, entries, overriddenKeys =
|
|
|
78
78
|
}
|
|
79
79
|
const chunk = chunks[index];
|
|
80
80
|
if (chunk === undefined)
|
|
81
|
-
throw new Error("
|
|
81
|
+
throw new Error("Slopify hit an internal error (a narration chunk went missing). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
82
82
|
while ((matches[matchIndex]?.end ?? Infinity) <= chunk.end)
|
|
83
83
|
matchIndex++;
|
|
84
84
|
const match = matches[matchIndex];
|
|
@@ -90,7 +90,7 @@ export function pronunciationChunks(source, chunking, entries, overriddenKeys =
|
|
|
90
90
|
continue;
|
|
91
91
|
const first = chunks[from];
|
|
92
92
|
if (first === undefined)
|
|
93
|
-
throw new Error("
|
|
93
|
+
throw new Error("Slopify hit an internal error (a group of narration chunks is empty). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
94
94
|
const text = source.slice(first.start, chunk.end);
|
|
95
95
|
result.push(from === index
|
|
96
96
|
? { key: chunk.key, text }
|
|
@@ -105,7 +105,7 @@ export function pronunciationMatches(source, entries) {
|
|
|
105
105
|
continue;
|
|
106
106
|
const entry = ordered.find((_, index) => match[index + 1] !== undefined);
|
|
107
107
|
if (entry === undefined)
|
|
108
|
-
throw new Error("
|
|
108
|
+
throw new Error("Slopify hit an internal error (a pronunciation glossary term has no pronunciation). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
109
109
|
matches.push({ start: match.index, end, entry });
|
|
110
110
|
}
|
|
111
111
|
return matches;
|
|
@@ -117,7 +117,7 @@ export function pronunciationSpans(source, entries) {
|
|
|
117
117
|
for (const [index, word] of Array.from(words).entries()) {
|
|
118
118
|
const ipa = match.entry.ipa[index];
|
|
119
119
|
if (ipa === undefined)
|
|
120
|
-
throw new Error("
|
|
120
|
+
throw new Error("Slopify hit an internal error (a pronunciation glossary term is missing a word). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
121
121
|
const start = match.start + word.index;
|
|
122
122
|
spans.push({ start, end: start + word[0].length, text: `/${ipa}/` });
|
|
123
123
|
}
|
|
@@ -14,7 +14,7 @@ import { planLegacyNarration } from "./legacy-plan.js";
|
|
|
14
14
|
import { observeNarration } from "./live.js";
|
|
15
15
|
// An empty narration source fails the stage immediately with no retries. Thrown from the
|
|
16
16
|
// slice rather than from a provider call, so the attempt wrapper never sees it.
|
|
17
|
-
export const nothingToNarrate = "nothing to narrate";
|
|
17
|
+
export const nothingToNarrate = "There is nothing to narrate because the article is empty. Add article text in Edit project → Article, then Retry stage.";
|
|
18
18
|
// What a chunk piece carries between runs: the text that was sent, and the file it came
|
|
19
19
|
// back as once the provider answered. That is the whole of the resume.
|
|
20
20
|
const chunkPayload = z.object({ text: z.string(), file: z.string().optional() });
|
|
@@ -35,13 +35,13 @@ export async function runNarration(deps, context, providers) {
|
|
|
35
35
|
deps.audioPreviews?.clear(projectId);
|
|
36
36
|
const project = projectById(deps.db, projectId);
|
|
37
37
|
if (project === undefined) {
|
|
38
|
-
throw new Error(
|
|
38
|
+
throw new Error("This project no longer exists; it may have been deleted while it was running.");
|
|
39
39
|
}
|
|
40
40
|
const choice = project.config.audio;
|
|
41
41
|
if (choice === undefined) {
|
|
42
42
|
// Admission refuses a run whose audio is Generate without a provider and a voice, so
|
|
43
43
|
// reaching here is a bug in admission rather than something the user did.
|
|
44
|
-
throw new Error("the
|
|
44
|
+
throw new Error("No voice is set for the narration. Choose a voice in Edit project → Providers, then Retry stage.");
|
|
45
45
|
}
|
|
46
46
|
// Read once, before anything is written: the segment steps below ask it what a previous
|
|
47
47
|
// run already stored, and the body step is the only thing that adds to it.
|
|
@@ -168,7 +168,7 @@ async function speakSegments(deps, context, providers, choice, outputs) {
|
|
|
168
168
|
if (text === "") {
|
|
169
169
|
// The same rule, applied to a segment: there is nothing to say, and silently
|
|
170
170
|
// dropping an intro the user picked would lose it without telling anyone.
|
|
171
|
-
throw new Error(`
|
|
171
|
+
throw new Error(`The ${segment.category} is empty, so there is nothing to narrate for it. Write the ${segment.category} or remove it in Edit project, then Retry stage.`);
|
|
172
172
|
}
|
|
173
173
|
const spoken = await providers.forPiece(piece.id).tts({
|
|
174
174
|
provider: choice.provider,
|
|
@@ -200,7 +200,7 @@ function segmentPieces(deps, projectId) {
|
|
|
200
200
|
if (article === undefined) {
|
|
201
201
|
// Admission writes all six stage rows with the project, so a project without an article
|
|
202
202
|
// stage is a bug rather than a run the user configured.
|
|
203
|
-
throw new Error(
|
|
203
|
+
throw new Error("Slopify hit an internal error (the project has no article stage). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
204
204
|
}
|
|
205
205
|
return piecesOf(deps.db, article.id, "segment");
|
|
206
206
|
}
|
|
@@ -257,13 +257,13 @@ function store(deps, projectId, role, path, durationMs, choice) {
|
|
|
257
257
|
}
|
|
258
258
|
function chunkOf(piece) {
|
|
259
259
|
if (piece.payload === null) {
|
|
260
|
-
throw new Error(
|
|
260
|
+
throw new Error("Slopify hit an internal error (a saved narration chunk is empty). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
261
261
|
}
|
|
262
262
|
return chunkPayload.parse(JSON.parse(piece.payload));
|
|
263
263
|
}
|
|
264
264
|
function segmentOf(piece) {
|
|
265
265
|
if (piece.payload === null) {
|
|
266
|
-
throw new Error(
|
|
266
|
+
throw new Error("Slopify hit an internal error (a saved intro or outro is empty). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
267
267
|
}
|
|
268
268
|
return segmentPayload.parse(JSON.parse(piece.payload));
|
|
269
269
|
}
|
|
@@ -9,14 +9,14 @@ function atoms(source, from, to, spans) {
|
|
|
9
9
|
const span = spans.get(at);
|
|
10
10
|
if (span !== undefined) {
|
|
11
11
|
if (span.end > to || span.end <= at)
|
|
12
|
-
throw new Error("
|
|
12
|
+
throw new Error("Slopify hit an internal error (a pronunciation fix spans more than one word). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
13
13
|
result.push({ text: span.text, spokenText: source.slice(at, span.end) });
|
|
14
14
|
at = span.end;
|
|
15
15
|
}
|
|
16
16
|
else {
|
|
17
17
|
const point = source.codePointAt(at);
|
|
18
18
|
if (point === undefined)
|
|
19
|
-
throw new Error("
|
|
19
|
+
throw new Error("Slopify hit an internal error (a narration position is outside the article text). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
20
20
|
const text = String.fromCodePoint(point);
|
|
21
21
|
result.push({ text, spokenText: text });
|
|
22
22
|
at += text.length;
|
|
@@ -34,7 +34,7 @@ function checkedSpans(source, pronunciation) {
|
|
|
34
34
|
span.end <= span.start ||
|
|
35
35
|
span.end > source.length ||
|
|
36
36
|
!/^[^\s\uD800-\uDFFF]+$/u.test(source.slice(span.start, span.end)))
|
|
37
|
-
throw new Error("
|
|
37
|
+
throw new Error("Slopify hit an internal error (pronunciation fixes overlap or split a word). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
38
38
|
spans.set(span.start, span);
|
|
39
39
|
end = span.end;
|
|
40
40
|
}
|
|
@@ -31,7 +31,7 @@ export function toAdmissionDraft(input) {
|
|
|
31
31
|
return { name: saved.name, mode: saved.mode };
|
|
32
32
|
fields.push({
|
|
33
33
|
field: category,
|
|
34
|
-
message: `That ${category} entry
|
|
34
|
+
message: `That ${category} entry was deleted. Choose another.`,
|
|
35
35
|
});
|
|
36
36
|
return undefined;
|
|
37
37
|
};
|
|
@@ -42,8 +42,8 @@ export function toAdmissionDraft(input) {
|
|
|
42
42
|
fields.push({
|
|
43
43
|
field,
|
|
44
44
|
message: attachment?.state === "copying" || attachment?.state === "pending"
|
|
45
|
-
? "This
|
|
46
|
-
: "That upload is no longer available
|
|
45
|
+
? "This file is still uploading. Wait for it to finish."
|
|
46
|
+
: "That upload is no longer available. Choose the file again.",
|
|
47
47
|
});
|
|
48
48
|
return undefined;
|
|
49
49
|
};
|
|
@@ -117,7 +117,8 @@ export function toAdmissionDraft(input) {
|
|
|
117
117
|
if (!parsed.success)
|
|
118
118
|
fields.push(...parsed.error.issues.map((issue) => ({
|
|
119
119
|
field: issue.path.join("."),
|
|
120
|
-
|
|
120
|
+
// The schema carries no sentences of its own; its defaults read like code.
|
|
121
|
+
message: "This setting is not valid. Reload the page and choose it again.",
|
|
121
122
|
})));
|
|
122
123
|
return fields.length || !parsed.success
|
|
123
124
|
? { ok: false, fields }
|
|
@@ -48,7 +48,7 @@ export async function checkDraftReadiness(deps, runs) {
|
|
|
48
48
|
field: c.field,
|
|
49
49
|
message: provider?.readiness.kind === "cli" && provider.readiness.issue
|
|
50
50
|
? provider.readiness.issue
|
|
51
|
-
: "
|
|
51
|
+
: "Set up this provider in Settings → Providers before starting.",
|
|
52
52
|
});
|
|
53
53
|
continue;
|
|
54
54
|
}
|
|
@@ -56,12 +56,12 @@ export async function checkDraftReadiness(deps, runs) {
|
|
|
56
56
|
if (result === "missing")
|
|
57
57
|
fields.push({
|
|
58
58
|
field: `${c.field}.model`,
|
|
59
|
-
message: "
|
|
59
|
+
message: "This model is no longer available. Choose another model.",
|
|
60
60
|
});
|
|
61
61
|
else if (result === "thinking")
|
|
62
62
|
fields.push({
|
|
63
63
|
field: `${c.field}.thinking`,
|
|
64
|
-
message: "
|
|
64
|
+
message: "This model does not support that thinking setting. Choose another.",
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
return { fields: [...fields, ...localDraftReadiness(deps, runs, providers)], providers };
|
|
@@ -73,15 +73,21 @@ export function localDraftReadiness(deps, runs, providers) {
|
|
|
73
73
|
if (cliPathChanged(deps.db, provider))
|
|
74
74
|
fields.push({
|
|
75
75
|
field: `${c.field}.cliPath`,
|
|
76
|
-
message: "
|
|
76
|
+
message: "This provider's command-line tool changed in Settings → Providers. Choose Review and start again.",
|
|
77
77
|
});
|
|
78
78
|
if (provider?.readiness.kind === "keyed" && !hasKey(deps.db, provider.id))
|
|
79
|
-
fields.push({
|
|
79
|
+
fields.push({
|
|
80
|
+
field: c.field,
|
|
81
|
+
message: "Add this provider's API key in Settings → Providers before starting.",
|
|
82
|
+
});
|
|
80
83
|
}
|
|
81
84
|
for (const { draft } of runs)
|
|
82
85
|
if (draft.sources.audio === "generate" &&
|
|
83
86
|
draft.audio &&
|
|
84
87
|
!listVoices(deps.db).some((v) => v.provider === draft.audio?.provider && v.voiceId === draft.audio.voice))
|
|
85
|
-
fields.push({
|
|
88
|
+
fields.push({
|
|
89
|
+
field: "audio.voice",
|
|
90
|
+
message: "Choose one of your saved voices. Add voices in Settings → Voices.",
|
|
91
|
+
});
|
|
86
92
|
return fields;
|
|
87
93
|
}
|
|
@@ -47,7 +47,10 @@ export function resolveReviewInputs(deps, view, font) {
|
|
|
47
47
|
if (!words.success)
|
|
48
48
|
fields.push({ field: "expectedWords", message: "Enter a whole number between 1 and 100000." });
|
|
49
49
|
if (document.variants.length > 49)
|
|
50
|
-
fields.push({
|
|
50
|
+
fields.push({
|
|
51
|
+
field: "variants",
|
|
52
|
+
message: "You can review at most 50 videos at once, counting the first one. Remove some variations.",
|
|
53
|
+
});
|
|
51
54
|
const converted = toAdmissionDraft({
|
|
52
55
|
document,
|
|
53
56
|
attachments: fresh.value.attachments,
|
|
@@ -96,7 +99,7 @@ export function resolveReviewInputs(deps, view, font) {
|
|
|
96
99
|
field: index > 0 && (issue.field === "title" || issue.field.startsWith("values."))
|
|
97
100
|
? `variants.${index - 1}.${issue.field}`
|
|
98
101
|
: issue.field,
|
|
99
|
-
message: `${index > 0 ? `
|
|
102
|
+
message: `${index > 0 ? `Video ${index + 1}: ` : ""}${issue.message}`,
|
|
100
103
|
})));
|
|
101
104
|
runs.push({
|
|
102
105
|
draft: accepted,
|
|
@@ -118,7 +121,7 @@ export function resolveReviewInputs(deps, view, font) {
|
|
|
118
121
|
if (!stat?.isFile() || font.id !== converted.draft.subtitles?.fontId)
|
|
119
122
|
fields.push({
|
|
120
123
|
field: "subtitles.fontId",
|
|
121
|
-
message: "
|
|
124
|
+
message: "This font was deleted. Choose another font.",
|
|
122
125
|
});
|
|
123
126
|
else
|
|
124
127
|
fontHash = createHash("sha256").update(readFileSync(font.path)).digest("hex");
|
|
@@ -133,7 +136,7 @@ export function resolveReviewInputs(deps, view, font) {
|
|
|
133
136
|
: run.draft.sources[stage] !== "generate")
|
|
134
137
|
fields.push({
|
|
135
138
|
field: `checkpoints.${stage}`,
|
|
136
|
-
message: "
|
|
139
|
+
message: "Checkpoints can only go before a step Slopify generates, or before export. Remove this checkpoint or turn that step on.",
|
|
137
140
|
});
|
|
138
141
|
if (fields.length)
|
|
139
142
|
return reviewRefusal(view, fields);
|
|
@@ -27,7 +27,7 @@ export async function resolvePlayReview(deps, view) {
|
|
|
27
27
|
return reviewRefusal(view, [
|
|
28
28
|
{
|
|
29
29
|
field: "subtitles.fontId",
|
|
30
|
-
message: "
|
|
30
|
+
message: "This font was deleted. Choose another font.",
|
|
31
31
|
},
|
|
32
32
|
]);
|
|
33
33
|
}
|
|
@@ -41,12 +41,12 @@ export async function resolvePlayReview(deps, view) {
|
|
|
41
41
|
if (result === "missing")
|
|
42
42
|
fields.push({
|
|
43
43
|
field: `${choice.field}.model`,
|
|
44
|
-
message: "
|
|
44
|
+
message: "This model is no longer available. Choose another model.",
|
|
45
45
|
});
|
|
46
46
|
else if (result === "thinking")
|
|
47
47
|
fields.push({
|
|
48
48
|
field: `${choice.field}.thinking`,
|
|
49
|
-
message: "
|
|
49
|
+
message: "This model does not support that thinking setting. Choose another.",
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
if (fields.length)
|
|
@@ -68,7 +68,9 @@ function attachmentsOf(deps, id) {
|
|
|
68
68
|
state,
|
|
69
69
|
stagedFileId: invalid ? null : row.staged_file_id,
|
|
70
70
|
bytes: invalid ? 0 : (staged?.bytes ?? 0),
|
|
71
|
-
error: invalid
|
|
71
|
+
error: invalid
|
|
72
|
+
? "This file is missing or did not finish uploading. Attach it again."
|
|
73
|
+
: row.error,
|
|
72
74
|
};
|
|
73
75
|
});
|
|
74
76
|
}
|
|
@@ -43,7 +43,7 @@ export async function uploadDraftAttachment(deps, input) {
|
|
|
43
43
|
if (!result.ok) {
|
|
44
44
|
deps.db
|
|
45
45
|
.prepare("UPDATE play_draft_attachments SET status='reattach',error=? WHERE id=? AND draft_id=? AND staged_file_id IS NULL")
|
|
46
|
-
.run("
|
|
46
|
+
.run("The upload failed. Attach the file again.", input.attachmentId, input.draftId);
|
|
47
47
|
return refuse("invalid-edit");
|
|
48
48
|
}
|
|
49
49
|
const updated = deps.db
|
|
@@ -92,14 +92,14 @@ export function admitPreview(deps, input) {
|
|
|
92
92
|
continue;
|
|
93
93
|
}
|
|
94
94
|
if (existing?.state === "running")
|
|
95
|
-
throw new Error("
|
|
95
|
+
throw new Error("Slopify hit an internal error (a step is already running for a different version of the project). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
96
96
|
deps.db
|
|
97
97
|
.prepare("DELETE FROM revision_work_reservations WHERE revision_id=? AND work_key=?")
|
|
98
98
|
.run(view.revision.id, recipe.key);
|
|
99
99
|
const key = reservationKey(recipe, view);
|
|
100
100
|
const fingerprint = snapshot.anchors[key];
|
|
101
101
|
if (fingerprint === undefined)
|
|
102
|
-
throw new Error("
|
|
102
|
+
throw new Error("Slopify hit an internal error (a step has no saved starting point). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
103
103
|
const work = insertInvocation(deps, view, recipe, catalogue, { key, fingerprint }, false, view.revision.id, admissionId);
|
|
104
104
|
workIds.push(work.workId);
|
|
105
105
|
}
|
|
@@ -117,7 +117,7 @@ export function legacyImageOutput(piece, outputs) {
|
|
|
117
117
|
row.meta.promptName === undefined ||
|
|
118
118
|
row.meta.promptName === promptName)));
|
|
119
119
|
if (matches.length > 1)
|
|
120
|
-
throw new Error(`
|
|
120
|
+
throw new Error(`Slopify hit an internal error (an image from an older Slopify version, ${piece.id}, matches multiple outputs). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
|
|
121
121
|
return matches[0];
|
|
122
122
|
}
|
|
123
123
|
const legacyImageMetadata = z
|
|
@@ -26,7 +26,7 @@ export function narrationFileRecipe(context, segment, parts) {
|
|
|
26
26
|
];
|
|
27
27
|
if (part.input.kind === "provided")
|
|
28
28
|
return [resourceIdentity(context, part), part.input.semantic, null];
|
|
29
|
-
throw new Error("
|
|
29
|
+
throw new Error("Slopify hit an internal error (a narration chunk has an unexpected source). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
30
30
|
}),
|
|
31
31
|
},
|
|
32
32
|
}, dependencies, { unresolved: parts.length === 0 });
|
|
@@ -7,7 +7,7 @@ export function visualRecipes(config, content, audioFingerprint, captionFingerpr
|
|
|
7
7
|
for (const key of imageKeys) {
|
|
8
8
|
const image = content.imageDefinitions[key];
|
|
9
9
|
if (image === undefined)
|
|
10
|
-
throw new Error("
|
|
10
|
+
throw new Error("Slopify hit an internal error (one of the project's images has no saved settings). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
11
11
|
const raw = image.templateKey === undefined || image.templateKey === null
|
|
12
12
|
? undefined
|
|
13
13
|
: content.promptTemplates[image.templateKey];
|
|
@@ -78,7 +78,7 @@ export async function recoverProject(deps, projectId, raw) {
|
|
|
78
78
|
const prior = retainedPreviewPlan(deps, base, deps.catalogue.read());
|
|
79
79
|
const action = input.action;
|
|
80
80
|
if (action.kind !== "rerun")
|
|
81
|
-
throw new Error("
|
|
81
|
+
throw new Error("Slopify hit an internal error (a recovery edit has nothing to re-run). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
82
82
|
const affected = dependentClosure(prior.recipes, sectionRoots(base, prior, action.stage));
|
|
83
83
|
const savedIntent = deps.db
|
|
84
84
|
.prepare("SELECT result_revision_id FROM revision_mutations WHERE project_id=? AND idempotency_key=?")
|
|
@@ -176,7 +176,7 @@ export async function recoverProject(deps, projectId, raw) {
|
|
|
176
176
|
storePreview(deps, preview.value.preview, preview.value.execution);
|
|
177
177
|
const stored = previewById(deps.db, projectId, preview.value.preview.id);
|
|
178
178
|
if (stored === undefined)
|
|
179
|
-
throw new Error("
|
|
179
|
+
throw new Error("Slopify hit an internal error (the saved recovery preview is missing). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
180
180
|
return {
|
|
181
181
|
plan: { preview: stored, execution: preview.value.execution },
|
|
182
182
|
view,
|
|
@@ -7,7 +7,7 @@ export { transitionRevisionWork } from "./transition-repo.js";
|
|
|
7
7
|
export function publicationTargets(db, publication, workKey, workFingerprint) {
|
|
8
8
|
const { work, pieceId, publicationId } = publication;
|
|
9
9
|
if (publicationId !== (pieceId ?? work.workId))
|
|
10
|
-
throw new Error("
|
|
10
|
+
throw new Error("Slopify hit an internal error (a finished result is filed under the wrong step). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
11
11
|
if (!workExists(db, work))
|
|
12
12
|
return [];
|
|
13
13
|
if (pieceId !== null) {
|
|
@@ -15,9 +15,9 @@ export function publicationTargets(db, publication, workKey, workFingerprint) {
|
|
|
15
15
|
.prepare("SELECT work_key,fingerprint FROM revision_work_pieces WHERE id=? AND work_id=?")
|
|
16
16
|
.get(pieceId, work.workId);
|
|
17
17
|
if (piece === undefined)
|
|
18
|
-
throw new Error("
|
|
18
|
+
throw new Error("Slopify hit an internal error (a finished result belongs to a different step). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
19
19
|
if (piece.work_key !== workKey || piece.fingerprint !== workFingerprint)
|
|
20
|
-
throw new Error("
|
|
20
|
+
throw new Error("Slopify hit an internal error (a finished result doesn't match its saved step). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
21
21
|
}
|
|
22
22
|
const currentId = currentRevisionId(db, work.projectId);
|
|
23
23
|
const owns = (revisionId) => {
|
|
@@ -18,11 +18,11 @@ export function reservationKey(recipe, view) {
|
|
|
18
18
|
export function admitInitialRevision(deps, view, catalogue) {
|
|
19
19
|
transact(deps.db, () => {
|
|
20
20
|
if (currentRevisionId(deps.db, view.revision.projectId) !== view.revision.id)
|
|
21
|
-
throw new Error("
|
|
21
|
+
throw new Error("The project was changed while this run was starting. Try again.");
|
|
22
22
|
if (deps.db
|
|
23
23
|
.prepare("SELECT 1 FROM revision_work WHERE project_id=? LIMIT 1")
|
|
24
24
|
.get(view.revision.projectId) !== undefined)
|
|
25
|
-
throw new Error("
|
|
25
|
+
throw new Error("Slopify hit an internal error (this project was started twice). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
26
26
|
const snapshot = executionCatalogue(catalogue, view.revision.config);
|
|
27
27
|
const plan = executionPlan(deps, view, snapshot);
|
|
28
28
|
for (const recipe of plan.recipes) {
|
|
@@ -32,7 +32,7 @@ export function admitInitialRevision(deps, view, catalogue) {
|
|
|
32
32
|
const logicalKey = recipe.input.kind === "tts" ? `${recipe.input.logicalKey}:1` : recipe.key;
|
|
33
33
|
const anchor = view.revision.fingerprints[logicalKey] ?? desired;
|
|
34
34
|
if (anchor === undefined)
|
|
35
|
-
throw new Error(`
|
|
35
|
+
throw new Error(`Slopify hit an internal error (step ${recipe.key} has no saved starting point). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
|
|
36
36
|
const reuse = plan.work.find((row) => row.key === recipe.key)?.disposition === "reuse";
|
|
37
37
|
if (reuse)
|
|
38
38
|
bindNarrationReuse(deps, view, recipe, narrationOrdinal(plan.recipes, recipe.key));
|
|
@@ -46,7 +46,7 @@ export function insertInvocation(deps, view, recipe, catalogue, anchor, complete
|
|
|
46
46
|
.prepare("SELECT id FROM stages WHERE project_id=? AND kind=?")
|
|
47
47
|
.get(projectId, recipe.stage);
|
|
48
48
|
if (typeof stage?.id !== "string")
|
|
49
|
-
throw new Error("
|
|
49
|
+
throw new Error("Slopify hit an internal error (a step has no matching stage). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
50
50
|
const work = {
|
|
51
51
|
projectId,
|
|
52
52
|
revisionId,
|
|
@@ -12,14 +12,14 @@ const answerSchema = z.object({
|
|
|
12
12
|
});
|
|
13
13
|
export async function executeArticleRequests(deps, context, providers, initial) {
|
|
14
14
|
if (initial.input.kind !== "llm")
|
|
15
|
-
throw new Error("
|
|
15
|
+
throw new Error("Slopify hit an internal error (the article step has no AI request). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
16
16
|
let piece = initial;
|
|
17
17
|
let text = "";
|
|
18
18
|
let inputTokens = 0;
|
|
19
19
|
let outputTokens = 0;
|
|
20
20
|
for (let part = 0; part <= continuationLimit; part += 1) {
|
|
21
21
|
if (piece.input.kind !== "llm")
|
|
22
|
-
throw new Error("
|
|
22
|
+
throw new Error("Slopify hit an internal error (an article continuation has no AI request). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
|
|
23
23
|
const saved = deps.db
|
|
24
24
|
.prepare("SELECT result_json FROM revision_work_pieces WHERE id=? AND work_id=?")
|
|
25
25
|
.get(piece.id, context.work.workId);
|
|
@@ -38,9 +38,9 @@ export async function executeArticleRequests(deps, context, providers, initial)
|
|
|
38
38
|
webSearch: input.webSearch,
|
|
39
39
|
previewLabel: part === 0 ? "Article" : `Continuation ${part}`,
|
|
40
40
|
check: (value) => value.text.trim() === ""
|
|
41
|
-
? "The article
|
|
41
|
+
? "The AI model returned an empty article. Retry stage; if it keeps happening, choose a different model in Edit project → Providers."
|
|
42
42
|
: part === continuationLimit && value.finishReason === "length"
|
|
43
|
-
? `The article was still unfinished after ${continuationLimit} continuations.`
|
|
43
|
+
? `The article was still unfinished after ${continuationLimit} continuations because the AI model kept stopping at its length limit. Ask for a shorter article in Edit project → Prompts, or choose a model with a larger output limit in Edit project → Providers, then Retry stage.`
|
|
44
44
|
: undefined,
|
|
45
45
|
}, (event) => {
|
|
46
46
|
if (event.type === "delta")
|
|
@@ -72,7 +72,7 @@ export async function executeArticleRequests(deps, context, providers, initial)
|
|
|
72
72
|
return { ok: false, reason: "held" };
|
|
73
73
|
piece = next;
|
|
74
74
|
}
|
|
75
|
-
throw new Error(
|
|
75
|
+
throw new Error(`The article was still unfinished after ${continuationLimit} continuations because the AI model kept stopping at its length limit. Ask for a shorter article in Edit project → Prompts, or choose a model with a larger output limit in Edit project → Providers, then Retry stage.`);
|
|
76
76
|
}
|
|
77
77
|
function continuationPiece(deps, context, initial, part, text) {
|
|
78
78
|
return transact(deps.db, () => {
|
|
@@ -11,14 +11,14 @@ export function admitReviewedCheckpoints(deps, projectIds, reviewed, catalogue)
|
|
|
11
11
|
const revisionId = currentRevisionId(deps.db, projectId);
|
|
12
12
|
const view = revisionId === undefined ? undefined : getRevisionView(deps, projectId, revisionId);
|
|
13
13
|
if (!view)
|
|
14
|
-
throw new Error("
|
|
14
|
+
throw new Error("Slopify hit an internal error (the project version to review is missing). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
15
15
|
const plan = executionPlan(deps, view, executionCatalogue(catalogue, view.revision.config));
|
|
16
16
|
const checkpoints = gates.map((gate) => {
|
|
17
17
|
const work = deps.db
|
|
18
18
|
.prepare("SELECT id FROM revision_work WHERE project_id=? AND revision_id=? AND kind=? ORDER BY id LIMIT 1")
|
|
19
19
|
.get(projectId, view.revision.id, gate.stage);
|
|
20
20
|
if (typeof work?.id !== "string")
|
|
21
|
-
throw new Error("
|
|
21
|
+
throw new Error("Slopify hit an internal error (a review checkpoint has no matching stage). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
22
22
|
return {
|
|
23
23
|
checkpointId: gate.checkpointId,
|
|
24
24
|
stage: gate.stage,
|
|
@@ -34,11 +34,11 @@ export function admitReviewedCheckpoints(deps, projectIds, reviewed, catalogue)
|
|
|
34
34
|
createdAt: deps.clock.now().toISOString(),
|
|
35
35
|
});
|
|
36
36
|
if (!saved.ok)
|
|
37
|
-
throw new Error("
|
|
37
|
+
throw new Error("Slopify hit an internal error (the review checkpoints could not be saved). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
38
38
|
return saved.value.map((row) => {
|
|
39
39
|
const gate = gates.find((gate) => gate.checkpointId === row.checkpointId);
|
|
40
40
|
if (!gate)
|
|
41
|
-
throw new Error("
|
|
41
|
+
throw new Error("Slopify hit an internal error (a saved review checkpoint could not be matched). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
42
42
|
return { ...row, reviewedFingerprint: gate.fingerprint };
|
|
43
43
|
});
|
|
44
44
|
});
|