@gentbajko/slopify 2.0.2 → 2.1.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/SUBTITLES.md +1 -1
- package/dist/adapters/alignment/protocol.js +11 -1
- package/dist/adapters/alignment/runner.js +4 -1
- package/dist/adapters/alignment/threads.js +11 -0
- package/dist/adapters/alignment/worker.js +66 -9
- package/dist/adapters/explain.js +93 -0
- package/dist/adapters/ffmpeg.js +3 -3
- package/dist/adapters/host-cli/index.js +34 -3
- 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/assets/document/SOURCE.txt +7 -0
- package/dist/assets/document/background.jpg +0 -0
- package/dist/assets/document/fonts/Cinzel-Black.ttf +0 -0
- package/dist/assets/document/fonts/Cinzel-Bold.ttf +0 -0
- package/dist/assets/document/fonts/Cinzel-Medium.ttf +0 -0
- package/dist/assets/document/fonts/Cinzel-Regular.ttf +0 -0
- package/dist/assets/document/fonts/OFL.txt +93 -0
- package/dist/assets/document/fonts/SOURCE.txt +11 -0
- package/dist/catalog/registry.js +13 -10
- package/dist/catalog/store.js +1 -1
- package/dist/catalog/validate.js +16 -6
- package/dist/edge/cli.js +33 -7
- 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 +11 -7
- package/dist/edge/docker-projects/tree.js +6 -6
- package/dist/edge/docker.js +13 -13
- package/dist/edge/host-cli.js +14 -0
- 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-schema.js +5 -8
- package/dist/edge/http/folder-location.js +13 -8
- package/dist/edge/http/fonts.js +22 -10
- package/dist/edge/http/host-cli.js +61 -14
- 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 +8 -4
- 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/open-folder.js +95 -0
- package/dist/host-cli/paths.js +6 -6
- package/dist/host-cli/runtime.js +2 -0
- 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 +27 -2
- package/dist/kernel/db/migrations/0013-schedule-topic-queue.sql +4 -0
- package/dist/kernel/db/migrations/0014-document-stage.sql +44 -0
- package/dist/kernel/db/migrations/0015-description-prompts.sql +13 -0
- package/dist/kernel/lock.js +4 -3
- package/dist/kernel/pipeline.js +11 -1
- package/dist/kernel/ports/host-cli.js +14 -0
- package/dist/kernel/ports/subtitles.js +15 -1
- package/dist/kernel/runner/attempt.js +26 -2
- package/dist/kernel/runner/graph.js +6 -1
- package/dist/main.js +8 -4
- package/dist/slices/admission/model.js +7 -1
- package/dist/slices/admission/rules.js +98 -22
- package/dist/slices/admission/schema.js +16 -3
- package/dist/slices/admission/start.js +3 -2
- 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/document/blocks.js +129 -0
- package/dist/slices/document/flow.js +117 -0
- package/dist/slices/document/fonts.js +54 -0
- package/dist/slices/document/model.js +11 -0
- package/dist/slices/document/pages.js +198 -0
- package/dist/slices/document/render.js +242 -0
- package/dist/slices/document/sources.js +35 -0
- package/dist/slices/document/theme.js +158 -0
- package/dist/slices/document/writer.js +66 -0
- package/dist/slices/estimate/index.js +14 -2
- 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/model.js +1 -1
- package/dist/slices/library/slots.js +6 -3
- 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 +35 -5
- package/dist/slices/play-drafts/readiness.js +15 -8
- package/dist/slices/play-drafts/review-inputs.js +7 -4
- package/dist/slices/play-drafts/review.js +3 -3
- package/dist/slices/play-drafts/schema.js +22 -5
- package/dist/slices/play-drafts/service.js +3 -1
- package/dist/slices/play-drafts/uploads.js +1 -1
- package/dist/slices/project-templates/from-project.js +10 -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 +11 -1
- package/dist/slices/rebuild/recipe-audio.js +3 -0
- package/dist/slices/rebuild/recipe-build.js +6 -1
- package/dist/slices/rebuild/recipe-document.js +36 -0
- package/dist/slices/rebuild/recipe-exports.js +14 -2
- package/dist/slices/rebuild/recipe-fixture.js +4 -0
- package/dist/slices/rebuild/recipe-legacy.js +6 -1
- package/dist/slices/rebuild/recipe-model.js +2 -0
- package/dist/slices/rebuild/recipe-narration-text.js +1 -1
- package/dist/slices/rebuild/recipe-provider-choice.js +3 -0
- package/dist/slices/rebuild/recipe-save.js +15 -3
- package/dist/slices/rebuild/recipe-validation.js +15 -2
- package/dist/slices/rebuild/recipe-visual.js +11 -2
- package/dist/slices/rebuild/recipe-work.js +15 -0
- package/dist/slices/rebuild/recipe-youtube.js +33 -0
- package/dist/slices/rebuild/recovery-selection.js +2 -0
- package/dist/slices/rebuild/recovery.js +12 -7
- package/dist/slices/rebuild/repo.js +3 -3
- package/dist/slices/rebuild/runtime-actions.js +2 -0
- 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-document.js +85 -0
- package/dist/slices/rebuild/runtime-export-inputs.js +7 -6
- package/dist/slices/rebuild/runtime-export.js +39 -23
- 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 +66 -0
- package/dist/slices/rebuild/runtime-provider.js +6 -4
- package/dist/slices/rebuild/runtime-publication.js +4 -3
- package/dist/slices/rebuild/runtime-run.js +22 -4
- package/dist/slices/rebuild/runtime-store.js +7 -2
- package/dist/slices/rebuild/runtime-subtitles.js +82 -16
- package/dist/slices/rebuild/runtime-youtube.js +84 -0
- package/dist/slices/rebuild/transition-repo.js +8 -6
- package/dist/slices/reruns/cascade.js +6 -4
- 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 +4 -2
- package/dist/slices/revisions/projection.js +2 -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/downloads.js +1 -0
- package/dist/slices/storage/layout.js +7 -1
- package/dist/slices/storage/model.js +3 -0
- 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/telemetry/model.js +1 -1
- package/dist/slices/thumbnail/run.js +8 -8
- package/dist/slices/video/audio-export.js +11 -3
- package/dist/slices/video/audio-inputs.js +2 -2
- package/dist/slices/video/edit-list.js +73 -0
- package/dist/slices/video/ffmpeg.js +129 -61
- package/dist/slices/video/motion.js +51 -0
- package/dist/slices/video/plan.js +62 -28
- package/dist/slices/video/reuse-audio.js +2 -0
- package/dist/slices/video/run.js +23 -12
- package/dist/slices/video/slideshow.js +48 -0
- package/dist/slices/video/write-export.js +17 -25
- package/dist/slices/youtube/answer.js +199 -0
- package/dist/slices/youtube/model.js +27 -0
- package/dist/slices/youtube/timestamps.js +24 -0
- package/dist/slices/youtube/transcript.js +47 -0
- 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-B6NbhpjD.css +1 -0
- package/dist/web/assets/index-ComF5gwP.js +142 -0
- package/dist/web/index.html +2 -2
- package/package.json +4 -2
- package/dist/web/assets/index-CZNiHm8P.css +0 -1
- package/dist/web/assets/index-DPgGLX5m.js +0 -137
|
@@ -5,7 +5,7 @@ import { stream } from "hono/streaming";
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { sniffImage } from "../../adapters/image/bytes.js";
|
|
7
7
|
import { redact } from "../../kernel/log.js";
|
|
8
|
-
import { bridgeLimits, hostCliIds, hostCliProtocol, hostFrameSchema, hostImageSchema, hostLlmIds, hostLlmSchema, hostModelsSchema, hostStatusSchema, } from "../../kernel/ports/host-cli.js";
|
|
8
|
+
import { bridgeLimits, HostFolderRefused, hostCliIds, hostCliProtocol, hostFrameSchema, hostImageSchema, hostLlmIds, hostLlmSchema, hostModelsSchema, hostOpenFolderSchema, hostStatusSchema, } from "../../kernel/ports/host-cli.js";
|
|
9
9
|
import { isProviderError, providerError } from "../../kernel/ports/model.js";
|
|
10
10
|
export function hostGate() {
|
|
11
11
|
let accepting = true;
|
|
@@ -48,14 +48,14 @@ export function hostGate() {
|
|
|
48
48
|
},
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
-
const unavailable = "
|
|
51
|
+
const unavailable = "The AI command-line tool on your computer stopped before it finished, so this result is uncertain. Check the section's outputs on the project page, then use Retry stage.";
|
|
52
52
|
function fault(error, token) {
|
|
53
53
|
return {
|
|
54
54
|
type: "error",
|
|
55
55
|
kind: isProviderError(error) ? error.fault.kind : "unavailable",
|
|
56
56
|
message: redact(isProviderError(error)
|
|
57
57
|
? error.message
|
|
58
|
-
: "
|
|
58
|
+
: "The Slopify helper on your computer could not run the AI command-line tool. Check the tool is installed and signed in, then run the Slopify Docker launcher again.")
|
|
59
59
|
.replaceAll(token, "[redacted]")
|
|
60
60
|
.slice(0, 4096),
|
|
61
61
|
};
|
|
@@ -67,7 +67,11 @@ export function hostCliRoutes(options) {
|
|
|
67
67
|
const supplied = Buffer.from(c.req.header("authorization") ?? "");
|
|
68
68
|
const expected = Buffer.from(`Bearer ${token}`);
|
|
69
69
|
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected))
|
|
70
|
-
return c.json({
|
|
70
|
+
return c.json({
|
|
71
|
+
type: "error",
|
|
72
|
+
kind: "unavailable",
|
|
73
|
+
message: "The Slopify helper on your computer refused the connection. Run the Slopify Docker launcher again to reconnect it.",
|
|
74
|
+
}, 401);
|
|
71
75
|
c.header("Cache-Control", "no-store");
|
|
72
76
|
await next();
|
|
73
77
|
});
|
|
@@ -78,13 +82,14 @@ export function hostCliRoutes(options) {
|
|
|
78
82
|
accepting: gate.accepting,
|
|
79
83
|
}));
|
|
80
84
|
app.use("/v1/*", async (c, next) => {
|
|
81
|
-
|
|
85
|
+
// Opening a folder is quick and must not look like running work to the launcher's upgrade check.
|
|
86
|
+
const kind = c.req.method === "POST" && c.req.path !== "/v1/open-folder" ? "generation" : "metadata";
|
|
82
87
|
const job = gate.acquire(kind);
|
|
83
88
|
if (!job)
|
|
84
89
|
return c.json({
|
|
85
90
|
type: "error",
|
|
86
91
|
kind: "unavailable",
|
|
87
|
-
message: "The
|
|
92
|
+
message: "The Slopify helper on your computer is busy or restarting. Wait a moment, then use Retry stage.",
|
|
88
93
|
}, 503);
|
|
89
94
|
const outgoing = c.env?.outgoing;
|
|
90
95
|
let responseEnded = !outgoing;
|
|
@@ -128,7 +133,11 @@ export function hostCliRoutes(options) {
|
|
|
128
133
|
});
|
|
129
134
|
app.use("/v1/*", bodyLimit({
|
|
130
135
|
maxSize: bridgeLimits.request,
|
|
131
|
-
onError: (c) => c.json({
|
|
136
|
+
onError: (c) => c.json({
|
|
137
|
+
type: "error",
|
|
138
|
+
kind: "unavailable",
|
|
139
|
+
message: "Slopify hit an internal error (a request to the helper on your computer was too large). Try again; if it keeps happening, use Download diagnostics in Settings and report it.",
|
|
140
|
+
}, 413),
|
|
132
141
|
}));
|
|
133
142
|
app.get("/v1/status/:provider", async (c) => {
|
|
134
143
|
const id = z.enum(hostCliIds).safeParse(c.req.param("provider"));
|
|
@@ -137,7 +146,10 @@ export function hostCliRoutes(options) {
|
|
|
137
146
|
const status = hostStatusSchema.parse(await ports.status(id.data));
|
|
138
147
|
const text = JSON.stringify(status);
|
|
139
148
|
if (Buffer.byteLength(text) > bridgeLimits.status)
|
|
140
|
-
throw providerError({
|
|
149
|
+
throw providerError({
|
|
150
|
+
kind: "unavailable",
|
|
151
|
+
message: "Slopify hit an internal error (the helper on your computer sent a status report that was too large). Try again; if it keeps happening, use Download diagnostics in Settings and report it.",
|
|
152
|
+
});
|
|
141
153
|
return c.json(status);
|
|
142
154
|
});
|
|
143
155
|
app.get("/v1/models/:provider", async (c) => {
|
|
@@ -148,7 +160,10 @@ export function hostCliRoutes(options) {
|
|
|
148
160
|
models: await (id.data === "codex-image" ? ports.image : ports.llm(id.data)).models(),
|
|
149
161
|
});
|
|
150
162
|
if (Buffer.byteLength(JSON.stringify(result)) > bridgeLimits.models)
|
|
151
|
-
throw providerError({
|
|
163
|
+
throw providerError({
|
|
164
|
+
kind: "unavailable",
|
|
165
|
+
message: "Slopify hit an internal error (the helper on your computer sent a model list that was too large). Try again; if it keeps happening, use Download diagnostics in Settings and report it.",
|
|
166
|
+
});
|
|
152
167
|
return c.json(result);
|
|
153
168
|
});
|
|
154
169
|
app.post("/v1/llm/:provider", async (c) => {
|
|
@@ -177,11 +192,11 @@ export function hostCliRoutes(options) {
|
|
|
177
192
|
timer = setTimeout(() => controller.abort(), 120_000);
|
|
178
193
|
const parsed = hostFrameSchema.parse(event);
|
|
179
194
|
if (done || parsed.type === "error")
|
|
180
|
-
throw new Error("
|
|
195
|
+
throw new Error("Slopify hit an internal error (the AI tool on your computer sent a reply Slopify could not read). Try again; if it keeps happening, use Download diagnostics in Settings and report it.");
|
|
181
196
|
const frame = `${JSON.stringify(parsed)}\n`;
|
|
182
197
|
total += Buffer.byteLength(frame);
|
|
183
198
|
if (Buffer.byteLength(frame) > bridgeLimits.frame || total > bridgeLimits.stream)
|
|
184
|
-
throw new Error("
|
|
199
|
+
throw new Error("Slopify hit an internal error (the AI tool on your computer sent a reply that was too large). Try again; if it keeps happening, use Download diagnostics in Settings and report it.");
|
|
185
200
|
await output.write(frame);
|
|
186
201
|
signal.throwIfAborted();
|
|
187
202
|
done = parsed.type === "done";
|
|
@@ -217,7 +232,7 @@ export function hostCliRoutes(options) {
|
|
|
217
232
|
if (signal.aborted)
|
|
218
233
|
throw providerError({ kind: "unavailable", message: unavailable });
|
|
219
234
|
if (result.bytes.byteLength > bridgeLimits.image || sniffImage(result.bytes) !== result.mime)
|
|
220
|
-
throw new Error("
|
|
235
|
+
throw new Error("Slopify hit an internal error (the image made on your computer was too large or not a valid image). Try again; if it keeps happening, use Download diagnostics in Settings and report it.");
|
|
221
236
|
c.header("Content-Type", result.mime);
|
|
222
237
|
c.header("Content-Length", String(result.bytes.byteLength));
|
|
223
238
|
return c.body(new Uint8Array(result.bytes));
|
|
@@ -230,8 +245,36 @@ export function hostCliRoutes(options) {
|
|
|
230
245
|
controller.abort();
|
|
231
246
|
}
|
|
232
247
|
});
|
|
248
|
+
app.post("/v1/open-folder", async (c) => {
|
|
249
|
+
const open = ports.openFolder;
|
|
250
|
+
if (!open)
|
|
251
|
+
return c.notFound();
|
|
252
|
+
const body = hostOpenFolderSchema.safeParse(await json(c.req.raw));
|
|
253
|
+
if (!body.success)
|
|
254
|
+
return invalid(c);
|
|
255
|
+
try {
|
|
256
|
+
await open(body.data.path);
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
const refused = error instanceof HostFolderRefused;
|
|
260
|
+
return c.json({
|
|
261
|
+
type: "error",
|
|
262
|
+
kind: "unavailable",
|
|
263
|
+
message: (error instanceof Error
|
|
264
|
+
? error.message
|
|
265
|
+
: "The Slopify host helper could not open the folder. Copy the path into your file manager instead.")
|
|
266
|
+
.replaceAll(token, "[redacted]")
|
|
267
|
+
.slice(0, 4096),
|
|
268
|
+
}, refused ? 403 : 503);
|
|
269
|
+
}
|
|
270
|
+
return c.json({ opened: true });
|
|
271
|
+
});
|
|
233
272
|
app.onError((error, c) => c.json(fault(error, token), 503));
|
|
234
|
-
app.notFound((c) => c.json({
|
|
273
|
+
app.notFound((c) => c.json({
|
|
274
|
+
type: "error",
|
|
275
|
+
kind: "unavailable",
|
|
276
|
+
message: "The Slopify helper on your computer does not match this version of Slopify. Run the Slopify Docker launcher again to update it.",
|
|
277
|
+
}, 404));
|
|
235
278
|
return app;
|
|
236
279
|
}
|
|
237
280
|
async function json(request) {
|
|
@@ -243,5 +286,9 @@ async function json(request) {
|
|
|
243
286
|
}
|
|
244
287
|
}
|
|
245
288
|
function invalid(c) {
|
|
246
|
-
return c.json({
|
|
289
|
+
return c.json({
|
|
290
|
+
type: "error",
|
|
291
|
+
kind: "unavailable",
|
|
292
|
+
message: "Slopify hit an internal error (the helper on your computer received a request it could not read). Try again; if it keeps happening, use Download diagnostics in Settings and report it.",
|
|
293
|
+
}, 400);
|
|
247
294
|
}
|
|
@@ -6,10 +6,12 @@ export async function readMultipart(request, consume) {
|
|
|
6
6
|
const contentType = request.headers.get("content-type");
|
|
7
7
|
if (contentType === null || !contentType.startsWith("multipart/form-data"))
|
|
8
8
|
throw new HTTPException(415, {
|
|
9
|
-
message: "
|
|
9
|
+
message: "The upload was not sent as a file. Reload the page and choose the file again.",
|
|
10
10
|
});
|
|
11
11
|
if (request.body === null)
|
|
12
|
-
throw new HTTPException(400, {
|
|
12
|
+
throw new HTTPException(400, {
|
|
13
|
+
message: "The upload arrived empty. Choose the file again and retry.",
|
|
14
|
+
});
|
|
13
15
|
let parser;
|
|
14
16
|
try {
|
|
15
17
|
parser = new Busboy({
|
|
@@ -20,7 +22,7 @@ export async function readMultipart(request, consume) {
|
|
|
20
22
|
}
|
|
21
23
|
catch (error) {
|
|
22
24
|
throw new HTTPException(400, {
|
|
23
|
-
message: "The
|
|
25
|
+
message: "The upload did not arrive complete. Choose the file again and retry.",
|
|
24
26
|
cause: error,
|
|
25
27
|
});
|
|
26
28
|
}
|
|
@@ -43,7 +45,7 @@ export async function readMultipart(request, consume) {
|
|
|
43
45
|
};
|
|
44
46
|
source.on("error", fail);
|
|
45
47
|
parser.on("error", (error) => fail(new HTTPException(400, {
|
|
46
|
-
message: "The
|
|
48
|
+
message: "The upload did not arrive complete. Choose the file again and retry.",
|
|
47
49
|
cause: error,
|
|
48
50
|
})));
|
|
49
51
|
parser.on("finish", () => complete());
|
|
@@ -62,7 +64,7 @@ export async function readMultipart(request, consume) {
|
|
|
62
64
|
fail(error instanceof Error ? error : new Error("Upload failed", { cause: error }));
|
|
63
65
|
});
|
|
64
66
|
});
|
|
65
|
-
const abort = () => fail(new Error("
|
|
67
|
+
const abort = () => fail(new Error("The upload was interrupted before it finished. Choose the file again and retry."));
|
|
66
68
|
request.signal.addEventListener("abort", abort, { once: true });
|
|
67
69
|
if (request.signal.aborted)
|
|
68
70
|
abort();
|
|
@@ -23,7 +23,7 @@ export function openFolderRoutes(deps) {
|
|
|
23
23
|
return problem(c, {
|
|
24
24
|
status: 403,
|
|
25
25
|
title: titleOf(403),
|
|
26
|
-
detail: "
|
|
26
|
+
detail: "For your safety, folders can only be opened from the Slopify page itself. Open Slopify and try again there.",
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
const { id } = c.req.valid("param");
|
|
@@ -41,7 +41,7 @@ export function openFolderRoutes(deps) {
|
|
|
41
41
|
return problem(c, {
|
|
42
42
|
status: 404,
|
|
43
43
|
title: titleOf(404),
|
|
44
|
-
detail: "
|
|
44
|
+
detail: "This output's file was deleted or moved. Use Re-run section on the project page to make it again.",
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
return replyForFolder(c, deps, id, download.download.path);
|
|
@@ -66,7 +66,7 @@ export function planningRoutes(deps) {
|
|
|
66
66
|
return problem(c, {
|
|
67
67
|
status: 400,
|
|
68
68
|
title: titleOf(400),
|
|
69
|
-
detail: "
|
|
69
|
+
detail: "Some settings need fixing before Slopify can estimate the cost. Fix the highlighted fields.",
|
|
70
70
|
extensions: { fields },
|
|
71
71
|
});
|
|
72
72
|
return c.json({
|
|
@@ -79,7 +79,7 @@ export function planningRoutes(deps) {
|
|
|
79
79
|
return problem(c, {
|
|
80
80
|
status: 409,
|
|
81
81
|
title: titleOf(409),
|
|
82
|
-
detail: "
|
|
82
|
+
detail: "Videos with checkpoints turned on cannot be queued. Start them one at a time from Play with Review and start.",
|
|
83
83
|
});
|
|
84
84
|
if (batchExists(deps.db, input.requestId))
|
|
85
85
|
return c.json({ queue: queueEntries(deps.db, input.requestId) });
|
|
@@ -92,7 +92,7 @@ export function planningRoutes(deps) {
|
|
|
92
92
|
return problem(c, {
|
|
93
93
|
status: 400,
|
|
94
94
|
title: titleOf(400),
|
|
95
|
-
detail: "
|
|
95
|
+
detail: "The subtitle font you picked is no longer available. Choose another font before starting.",
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -102,7 +102,7 @@ export function planningRoutes(deps) {
|
|
|
102
102
|
return problem(c, {
|
|
103
103
|
status: 400,
|
|
104
104
|
title: titleOf(400),
|
|
105
|
-
detail: "No videos were queued.
|
|
105
|
+
detail: "No videos were queued. Fix the highlighted fields, then try again.",
|
|
106
106
|
extensions: { fields },
|
|
107
107
|
});
|
|
108
108
|
const queue = enqueueBatch({ ...deps, emit: (event) => deps.hub.emitGlobal(event) }, input.requestId, runs);
|
|
@@ -38,7 +38,7 @@ export function problemFromError(c, error, deps) {
|
|
|
38
38
|
return problem(c, {
|
|
39
39
|
status: 500,
|
|
40
40
|
title: titleOf(500),
|
|
41
|
-
detail: `
|
|
41
|
+
detail: `Slopify hit an internal error while handling this request. Try again; if it keeps happening, use Download diagnostics in Settings and report it (reference ${correlationId}).`,
|
|
42
42
|
extensions: { correlationId },
|
|
43
43
|
});
|
|
44
44
|
}
|
|
@@ -54,7 +54,7 @@ export function onInvalid(result, c) {
|
|
|
54
54
|
return problem(c, {
|
|
55
55
|
status: 400,
|
|
56
56
|
title: titleOf(400),
|
|
57
|
-
detail: "
|
|
57
|
+
detail: "Slopify could not read this request, which usually means the page is out of date. Reload the page and try again.",
|
|
58
58
|
extensions: {
|
|
59
59
|
errors: result.error.issues.map((issue) => ({
|
|
60
60
|
path: issue.path.join("."),
|
|
@@ -17,10 +17,14 @@ function refused(c, result) {
|
|
|
17
17
|
status,
|
|
18
18
|
title: titleOf(status),
|
|
19
19
|
detail: result.reason === "referenced-by-schedule"
|
|
20
|
-
? "
|
|
20
|
+
? "Some schedules still use this template. In Library > Schedules, cancel and delete them first; their run history is kept."
|
|
21
21
|
: result.reason === "missing-prompt"
|
|
22
|
-
? "A
|
|
23
|
-
:
|
|
22
|
+
? "A prompt or intro/outro entry this template uses was deleted. Choose another in the template, then save again."
|
|
23
|
+
: result.reason === "not-found"
|
|
24
|
+
? "This template no longer exists; it may have been deleted. Go to Library > Templates to see your templates."
|
|
25
|
+
: result.reason === "conflict"
|
|
26
|
+
? "This template or its project changed while you were editing. Reload the page to see the latest, then make your change again."
|
|
27
|
+
: "Some template settings are not valid, for example an image you supplied yourself. Check the template's settings and try again.",
|
|
24
28
|
extensions: { reason: result.reason },
|
|
25
29
|
});
|
|
26
30
|
}
|
|
@@ -60,7 +60,7 @@ export function projectRoutes(deps) {
|
|
|
60
60
|
return problem(c, {
|
|
61
61
|
status: 409,
|
|
62
62
|
title: titleOf(409),
|
|
63
|
-
detail: "
|
|
63
|
+
detail: "This project has checkpoints turned on, so it must be started from Play with Review and start.",
|
|
64
64
|
});
|
|
65
65
|
const requestedSubtitles = c.req.valid("json").subtitles;
|
|
66
66
|
if (requestedSubtitles !== undefined && requestedSubtitles.mode !== "off") {
|
|
@@ -71,12 +71,12 @@ export function projectRoutes(deps) {
|
|
|
71
71
|
return problem(c, {
|
|
72
72
|
status: 400,
|
|
73
73
|
title: titleOf(400),
|
|
74
|
-
detail: "
|
|
74
|
+
detail: "The subtitle font you picked is no longer available. Choose another font before starting.",
|
|
75
75
|
extensions: {
|
|
76
76
|
fields: [
|
|
77
77
|
{
|
|
78
78
|
field: "subtitles.fontId",
|
|
79
|
-
message: "This font is no longer available
|
|
79
|
+
message: "This font is no longer available. Choose another or upload it again in Settings.",
|
|
80
80
|
},
|
|
81
81
|
],
|
|
82
82
|
},
|
|
@@ -96,7 +96,7 @@ export function projectRoutes(deps) {
|
|
|
96
96
|
return problem(c, {
|
|
97
97
|
status: 400,
|
|
98
98
|
title: titleOf(400),
|
|
99
|
-
detail: "This
|
|
99
|
+
detail: "This video cannot start yet. Fix the highlighted fields, then try again.",
|
|
100
100
|
extensions: {
|
|
101
101
|
fields: [...picked.missing, ...modelErrors, ...(admitted.ok ? [] : admitted.fields)],
|
|
102
102
|
},
|
|
@@ -136,7 +136,7 @@ export function projectRoutes(deps) {
|
|
|
136
136
|
return problem(c, {
|
|
137
137
|
status: 404,
|
|
138
138
|
title: titleOf(404),
|
|
139
|
-
detail: "
|
|
139
|
+
detail: "This project no longer exists. Go back to Projects to pick another.",
|
|
140
140
|
});
|
|
141
141
|
}
|
|
142
142
|
if (deps.catalogue !== undefined)
|
|
@@ -171,8 +171,8 @@ const deleteStatus = {
|
|
|
171
171
|
files: 500,
|
|
172
172
|
};
|
|
173
173
|
const deleteDetails = {
|
|
174
|
-
"no-project": "
|
|
174
|
+
"no-project": "This project no longer exists. Go back to Projects to pick another.",
|
|
175
175
|
// The run has to be stopped before its files can go.
|
|
176
|
-
running: "This project is still running. Cancel the
|
|
177
|
-
files: "Some of this project's files could not be removed.",
|
|
176
|
+
running: "This project is still running. Use Cancel run on the project page first, then delete it.",
|
|
177
|
+
files: "Some of this project's files could not be removed. Close any program using files in the project folder, then try deleting again.",
|
|
178
178
|
};
|
|
@@ -50,7 +50,7 @@ export function refused(c, failure, noun) {
|
|
|
50
50
|
return problem(c, {
|
|
51
51
|
status: 400,
|
|
52
52
|
title: titleOf(400),
|
|
53
|
-
detail: `This ${noun} cannot be saved
|
|
53
|
+
detail: `This ${noun} cannot be saved yet. Fix the highlighted fields and try again.`,
|
|
54
54
|
extensions: { fields: failure.fields },
|
|
55
55
|
});
|
|
56
56
|
// The name is refused against a row that exists, and the form marks the field.
|
|
@@ -58,16 +58,21 @@ export function refused(c, failure, noun) {
|
|
|
58
58
|
return problem(c, {
|
|
59
59
|
status: 409,
|
|
60
60
|
title: titleOf(409),
|
|
61
|
-
detail: `Another ${noun} already has this name.`,
|
|
61
|
+
detail: `Another ${noun} already has this name. Choose a different name.`,
|
|
62
62
|
extensions: {
|
|
63
|
-
fields: [
|
|
63
|
+
fields: [
|
|
64
|
+
{
|
|
65
|
+
field: "name",
|
|
66
|
+
message: `Another ${noun} already has this name. Choose a different name.`,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
64
69
|
},
|
|
65
70
|
});
|
|
66
71
|
case "not-found":
|
|
67
72
|
return problem(c, {
|
|
68
73
|
status: 404,
|
|
69
74
|
title: titleOf(404),
|
|
70
|
-
detail: `
|
|
75
|
+
detail: `This ${noun} no longer exists; it may have been deleted. Go back to the Library to pick another.`,
|
|
71
76
|
});
|
|
72
77
|
}
|
|
73
78
|
}
|
|
@@ -29,10 +29,16 @@ export function providerRoutes(deps) {
|
|
|
29
29
|
report: (provider) => deps.log.write("warn", "model-catalog", { detail: `Could not load models for ${provider}` }),
|
|
30
30
|
});
|
|
31
31
|
return (new Hono()
|
|
32
|
-
.get("/catalogue", (c) => c.json(deps.catalogue?.status() ?? {
|
|
32
|
+
.get("/catalogue", (c) => c.json(deps.catalogue?.status() ?? {
|
|
33
|
+
warning: "The model list (models.yaml) is not loaded yet. Wait a moment and reload the page; if it stays like this, restart Slopify.",
|
|
34
|
+
}))
|
|
33
35
|
.post("/catalogue/refresh", async (c) => {
|
|
34
36
|
if (!deps.catalogue)
|
|
35
|
-
return problem(c, {
|
|
37
|
+
return problem(c, {
|
|
38
|
+
status: 503,
|
|
39
|
+
title: titleOf(503),
|
|
40
|
+
detail: "The model list (models.yaml) is not loaded yet. Wait a moment and reload the page; if it stays like this, restart Slopify.",
|
|
41
|
+
});
|
|
36
42
|
try {
|
|
37
43
|
await deps.catalogue.refresh();
|
|
38
44
|
}
|
|
@@ -40,7 +46,7 @@ export function providerRoutes(deps) {
|
|
|
40
46
|
return problem(c, {
|
|
41
47
|
status: 502,
|
|
42
48
|
title: titleOf(502),
|
|
43
|
-
detail: "
|
|
49
|
+
detail: "Slopify could not download the latest model list. Your current list is still in use; check your internet connection and try Refresh again.",
|
|
44
50
|
});
|
|
45
51
|
}
|
|
46
52
|
return c.json(deps.catalogue.status());
|
|
@@ -104,20 +110,22 @@ function refusal(c, id, reason) {
|
|
|
104
110
|
return problem(c, {
|
|
105
111
|
status: 400,
|
|
106
112
|
title: titleOf(400),
|
|
107
|
-
detail: `${name} signs in through its own
|
|
113
|
+
detail: `${name} signs in through its own command-line tool, so it has no API key to save here. Sign in with that tool on your computer instead.`,
|
|
108
114
|
});
|
|
109
115
|
}
|
|
110
116
|
if (reason === "absent") {
|
|
111
117
|
return problem(c, {
|
|
112
118
|
status: 404,
|
|
113
119
|
title: titleOf(404),
|
|
114
|
-
detail: `
|
|
120
|
+
detail: `There is no saved API key for ${name}, so there is nothing to remove.`,
|
|
115
121
|
});
|
|
116
122
|
}
|
|
117
123
|
return problem(c, {
|
|
118
124
|
status: 400,
|
|
119
125
|
title: titleOf(400),
|
|
120
|
-
detail: "This key cannot be saved
|
|
121
|
-
extensions: {
|
|
126
|
+
detail: "This API key cannot be saved yet. Paste the key and try again.",
|
|
127
|
+
extensions: {
|
|
128
|
+
fields: [{ field: "key", message: "Paste the API key from your provider's account page." }],
|
|
129
|
+
},
|
|
122
130
|
});
|
|
123
131
|
}
|
|
@@ -33,10 +33,14 @@ export function revisionFileRoutes(deps) {
|
|
|
33
33
|
if (!result.ok)
|
|
34
34
|
return unavailable(c, result.reason);
|
|
35
35
|
const value = result.download;
|
|
36
|
+
// `?inline=1` lets the project page open a PDF in a browser tab instead of saving
|
|
37
|
+
// it. Only PDFs: anything else the browser might render stays a download.
|
|
38
|
+
const inline = c.req.query("inline") === "1" && value.contentType === "application/pdf";
|
|
36
39
|
return c.body(Readable.toWeb(createReadStream(value.path)), 200, {
|
|
37
40
|
"content-type": value.contentType,
|
|
38
41
|
"content-length": String(value.bytes),
|
|
39
|
-
"content-disposition":
|
|
42
|
+
"content-disposition": `${inline ? "inline" : "attachment"}; filename="${value.filename}"`,
|
|
43
|
+
"x-content-type-options": "nosniff",
|
|
40
44
|
});
|
|
41
45
|
});
|
|
42
46
|
}
|
|
@@ -47,7 +51,7 @@ export function revisionFolderRoutes(deps) {
|
|
|
47
51
|
return problem(c, {
|
|
48
52
|
status: 403,
|
|
49
53
|
title: titleOf(403),
|
|
50
|
-
detail: "
|
|
54
|
+
detail: "For your safety, folders can only be opened from the Slopify page itself. Open Slopify and try again there.",
|
|
51
55
|
});
|
|
52
56
|
const { id: projectId, revisionId, recordId } = c.req.valid("param");
|
|
53
57
|
const result = findRevisionDownload(deps, projectId, revisionId, recordId);
|
|
@@ -61,8 +65,8 @@ function unavailable(c, reason) {
|
|
|
61
65
|
status: 404,
|
|
62
66
|
title: titleOf(404),
|
|
63
67
|
detail: reason === "missing-file"
|
|
64
|
-
? "This
|
|
65
|
-
: "
|
|
68
|
+
? "This file was deleted or moved from the project folder. In the Edit tab, use Rebuild affected outputs to make it again."
|
|
69
|
+
: "This file is no longer part of the project. Reload the page to see its current files.",
|
|
66
70
|
extensions: { reason },
|
|
67
71
|
});
|
|
68
72
|
}
|
|
@@ -3,6 +3,7 @@ import { Hono } from "hono";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { projectExists } from "../../slices/admission/repo.js";
|
|
5
5
|
import { previewRebuildSchema, startRebuildSchema } from "../../slices/rebuild/model.js";
|
|
6
|
+
import { narrationChunkOrder } from "../../slices/rebuild/runtime-narration-text.js";
|
|
6
7
|
import { previewRebuild, startRebuild } from "../../slices/rebuild/service.js";
|
|
7
8
|
import { ensureBaseline, restoreRevision, saveRevision } from "../../slices/revisions/index.js";
|
|
8
9
|
import { currentRevisionId, listRevisionHistory } from "../../slices/revisions/repo.js";
|
|
@@ -30,12 +31,24 @@ const statuses = {
|
|
|
30
31
|
"cost-ack-required": 409,
|
|
31
32
|
readiness: 409,
|
|
32
33
|
};
|
|
34
|
+
const details = {
|
|
35
|
+
"no-project": "This project no longer exists. Go back to Projects to pick another.",
|
|
36
|
+
"no-revision": "This saved version of the project no longer exists. Reload the page, then try again.",
|
|
37
|
+
conflict: "This project changed while you were editing. Reload the page to see the latest, then make your change again.",
|
|
38
|
+
"idempotency-conflict": "This change was already sent with different details. Reload the page, then try again.",
|
|
39
|
+
"invalid-edit": "Some of your changes are not valid. Fix the highlighted fields and save again.",
|
|
40
|
+
"stale-preview": "What needs rebuilding changed since you opened this review. Cancel it and open Rebuild affected outputs again to see the latest.",
|
|
41
|
+
"invalid-selection": "Some of the outputs you picked can no longer be rebuilt. Reload the page and choose again.",
|
|
42
|
+
"review-required": 'This rebuild touches content you supplied yourself. Tick "Keep the provided content" for each item in the review, then choose Start rebuild.',
|
|
43
|
+
"cost-ack-required": "Some costs for this rebuild are unknown. Tick the box confirming you understand, then choose Start rebuild.",
|
|
44
|
+
readiness: "Slopify cannot run this rebuild yet. Fix the problems listed, then try again.",
|
|
45
|
+
};
|
|
33
46
|
function refused(c, deps, projectId, value) {
|
|
34
47
|
const status = statuses[value.reason];
|
|
35
48
|
return problem(c, {
|
|
36
49
|
status,
|
|
37
50
|
title: titleOf(status),
|
|
38
|
-
detail: value.reason
|
|
51
|
+
detail: details[value.reason],
|
|
39
52
|
extensions: {
|
|
40
53
|
reason: value.reason,
|
|
41
54
|
currentRevisionId: currentRevisionId(deps.db, projectId) ?? null,
|
|
@@ -47,11 +60,11 @@ function unavailable(c) {
|
|
|
47
60
|
return problem(c, {
|
|
48
61
|
status: 503,
|
|
49
62
|
title: titleOf(503),
|
|
50
|
-
detail: "
|
|
63
|
+
detail: "Rebuilding is not ready yet because Slopify is still starting. Wait a moment, reload the page and try again.",
|
|
51
64
|
});
|
|
52
65
|
}
|
|
53
66
|
export function revisionRoutes(deps) {
|
|
54
|
-
return new Hono()
|
|
67
|
+
return (new Hono()
|
|
55
68
|
.post("/:id/revisions/prepare", zValidator("param", projectParam, onInvalid), async (c) => {
|
|
56
69
|
const projectId = c.req.valid("param").id;
|
|
57
70
|
const result = await ensureBaseline(deps, projectId);
|
|
@@ -69,6 +82,19 @@ export function revisionRoutes(deps) {
|
|
|
69
82
|
return view === undefined
|
|
70
83
|
? refused(c, deps, projectId, { reason: "no-revision" })
|
|
71
84
|
: c.json({ view });
|
|
85
|
+
})
|
|
86
|
+
// Chunk keys in spoken order, so the Narration editor numbers chunks the way a subtitle
|
|
87
|
+
// error names them. Building the plan is the costly part, so it has its own request.
|
|
88
|
+
.get("/:id/revisions/:revisionId/narration-chunks", zValidator("param", revisionParam, onInvalid), (c) => {
|
|
89
|
+
const { id: projectId, revisionId } = c.req.valid("param");
|
|
90
|
+
let chunks;
|
|
91
|
+
try {
|
|
92
|
+
chunks = narrationChunkOrder(deps, projectId, revisionId);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
chunks = undefined;
|
|
96
|
+
}
|
|
97
|
+
return c.json({ chunks: chunks ?? null });
|
|
72
98
|
})
|
|
73
99
|
.post("/:id/revisions", zValidator("param", projectParam, onInvalid), zValidator("json", saveBody, onInvalid), async (c) => {
|
|
74
100
|
const projectId = c.req.valid("param").id;
|
|
@@ -99,5 +125,5 @@ export function revisionRoutes(deps) {
|
|
|
99
125
|
const projectId = c.req.valid("param").id;
|
|
100
126
|
const result = await startRebuild(deps.rebuild, { projectId, ...c.req.valid("json") });
|
|
101
127
|
return result.ok ? c.json(result, 202) : refused(c, deps, projectId, result);
|
|
102
|
-
});
|
|
128
|
+
}));
|
|
103
129
|
}
|
|
@@ -13,15 +13,15 @@ function refused(c, result) {
|
|
|
13
13
|
? 409
|
|
14
14
|
: 400;
|
|
15
15
|
const detail = {
|
|
16
|
-
"invalid-input": "Check
|
|
17
|
-
"not-found": "
|
|
18
|
-
conflict: "
|
|
19
|
-
"cancel-required": "Cancel
|
|
20
|
-
"missing-template": "The
|
|
21
|
-
"unsupported-media": "This template
|
|
22
|
-
"not-due": "
|
|
23
|
-
"spend-limit": "The
|
|
24
|
-
readiness: "
|
|
16
|
+
"invalid-input": "Some schedule settings are not valid. Check each field and save again.",
|
|
17
|
+
"not-found": "This schedule no longer exists; it may have been deleted. Reload the page to see your schedules.",
|
|
18
|
+
conflict: "This schedule changed while you were editing, possibly because a run just started. Reload the page to see the latest, then make your change again.",
|
|
19
|
+
"cancel-required": "This schedule is still active. Cancel it first, then delete it. Completed schedules can be deleted straight away.",
|
|
20
|
+
"missing-template": "The template you picked was deleted or changed. Choose a template again, then save.",
|
|
21
|
+
"unsupported-media": "This template uses audio, images or a thumbnail you supplied, so it cannot run on a schedule. Pick a template that generates these instead.",
|
|
22
|
+
"not-due": "This one-off time has already passed. Choose a time in the future.",
|
|
23
|
+
"spend-limit": "The estimated cost is above this schedule's spend limit, or some prices are unknown. Raise the spend limit or choose models with known prices.",
|
|
24
|
+
readiness: "A provider this template needs is not ready, for example a missing API key, model or voice. Check Settings → Providers, then try again.",
|
|
25
25
|
};
|
|
26
26
|
return problem(c, {
|
|
27
27
|
status,
|