@vanillaskyai/video 0.10.1 → 0.10.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/CHANGELOG.md +14 -0
- package/README.md +12 -7
- package/dist/check-runtime.js +2 -2
- package/dist/{chunk-6KZGF63O.js → chunk-3U2F7C7C.js} +53 -34
- package/dist/{chunk-5C6HZNHY.js → chunk-LJR5VLEI.js} +3 -2
- package/dist/{chunk-77VY4O7A.js → chunk-SI6CFFAA.js} +57 -10
- package/dist/{chunk-KHLO5OHT.js → chunk-UCXQORDZ.js} +1 -1
- package/dist/{chunk-VQH3JTQC.js → chunk-VVNZCY2U.js} +1 -1
- package/dist/{chunk-TVIU23OM.js → chunk-YZZSWGC3.js} +8 -3
- package/dist/{cinema-media-APDJS7SC.js → cinema-media-4JUYKPQI.js} +4 -4
- package/dist/{comparison-ZVE2DE7U.js → comparison-3QLMUC5V.js} +4 -4
- package/dist/{editorial-timeline-YIXXVJUV.js → editorial-timeline-IVE2P25N.js} +4 -4
- package/dist/{key-figure-I2C37FWV.js → key-figure-APD56LCO.js} +4 -4
- package/dist/{mobile-message-JSGBU6EX.js → mobile-message-WLK3WY5B.js} +4 -4
- package/dist/{quote-XH54G3FS.js → quote-Z6YET5BO.js} +4 -4
- package/dist/react.js +52 -19
- package/dist/{scene-video-backdrop-PCTARAAJ.js → scene-video-backdrop-OWUXPXHZ.js} +2 -2
- package/dist/server.js +333 -276
- package/docs/agent-integration.md +5 -4
- package/docs/getting-started.md +7 -5
- package/docs/media-and-audio.md +23 -17
- package/docs/production.md +7 -4
- package/docs/prompt-and-input.md +24 -15
- package/docs/provider-integration.md +2 -2
- package/docs/reference/protocol.md +7 -1
- package/docs/reference/provider-adapters.md +5 -3
- package/docs/testing.md +7 -2
- package/package.json +1 -1
- package/registry/items/backgrounds.json +2 -2
- package/registry/items/cinemaMedia.json +1 -1
- package/registry/items/comparison.json +1 -1
- package/registry/items/editorialTimeline.json +1 -1
- package/registry/items/keyFigure.json +1 -1
- package/registry/items/mobileMessage.json +1 -1
- package/registry/items/quote.json +1 -1
- package/starters/video-chat/README.md +18 -17
- package/starters/video-chat/package.json +1 -1
- package/starters/video-chat/providers/video.ts +5 -4
package/dist/server.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
invokeIsolated
|
|
7
7
|
} from "./chunk-5BSJLT6H.js";
|
|
8
8
|
import {
|
|
9
|
+
attachGenerationLifecycleSink,
|
|
9
10
|
getGenerationLifecycleSink
|
|
10
11
|
} from "./chunk-E7CL7UPB.js";
|
|
11
12
|
import {
|
|
@@ -55,69 +56,6 @@ import "./chunk-3O7OMMMF.js";
|
|
|
55
56
|
import "./chunk-2E6T633S.js";
|
|
56
57
|
import "./chunk-73NTSFFI.js";
|
|
57
58
|
|
|
58
|
-
// src/server/opening-continuity.ts
|
|
59
|
-
function continueAfterOpening(narration, earlier) {
|
|
60
|
-
const words = (value) => Array.from(value.matchAll(/[\p{L}\p{N}]+(?:['’][\p{L}\p{N}]+)*/gu));
|
|
61
|
-
let remaining = narration;
|
|
62
|
-
for (const previous of [...earlier].reverse()) {
|
|
63
|
-
const known = words(previous), current = words(remaining);
|
|
64
|
-
if (known.length < 5 || known.length > current.length) continue;
|
|
65
|
-
if (!known.every((word, index) => word[0].toLowerCase() === current[index][0].toLowerCase())) continue;
|
|
66
|
-
if (known.length === current.length) return "";
|
|
67
|
-
const last = current[known.length - 1];
|
|
68
|
-
const suffix = remaining.slice(last.index + last[0].length);
|
|
69
|
-
const boundary = suffix.match(/^[”"'’\])]*[.!?…]+[\s”"'’\])]*/u);
|
|
70
|
-
if (boundary) remaining = suffix.slice(boundary[0].length).trim();
|
|
71
|
-
}
|
|
72
|
-
return remaining;
|
|
73
|
-
}
|
|
74
|
-
function createOpeningContinuation(initialOpening) {
|
|
75
|
-
const earlier = initialOpening ? [initialOpening] : [];
|
|
76
|
-
let bodyStarted = false;
|
|
77
|
-
const copy = (authored, narration) => {
|
|
78
|
-
if (continueAfterOpening(authored, earlier) !== "") return authored;
|
|
79
|
-
const next = continueAfterOpening(narration, earlier).trim();
|
|
80
|
-
return next && [...next].length <= 65 && /[.!?…][”"’')\]]*$/u.test(next) ? next : authored;
|
|
81
|
-
};
|
|
82
|
-
return {
|
|
83
|
-
copy,
|
|
84
|
-
remember(line) {
|
|
85
|
-
if (line.trim()) earlier.push(line);
|
|
86
|
-
},
|
|
87
|
-
narration(line) {
|
|
88
|
-
return continueAfterOpening(line, earlier);
|
|
89
|
-
},
|
|
90
|
-
line(rawLine) {
|
|
91
|
-
if (bodyStarted || earlier.length === 0) return rawLine;
|
|
92
|
-
let part;
|
|
93
|
-
try {
|
|
94
|
-
part = JSON.parse(rawLine);
|
|
95
|
-
} catch {
|
|
96
|
-
return rawLine;
|
|
97
|
-
}
|
|
98
|
-
if (!part || part.type !== "scene.add" || part.placement === "closer") return rawLine;
|
|
99
|
-
const scene = part.scene;
|
|
100
|
-
if (!scene) return rawLine;
|
|
101
|
-
if (typeof scene.narration !== "string") {
|
|
102
|
-
bodyStarted = true;
|
|
103
|
-
return rawLine;
|
|
104
|
-
}
|
|
105
|
-
const narration = continueAfterOpening(scene.narration, earlier);
|
|
106
|
-
if (!narration && scene.templateId === "cinemaMedia") return null;
|
|
107
|
-
const variables = scene.variables;
|
|
108
|
-
if (!narration && scene.templateId === "chapterTitle" && typeof variables?.title === "string" && !continueAfterOpening(variables.title, earlier)) return null;
|
|
109
|
-
bodyStarted = true;
|
|
110
|
-
if (!narration) return rawLine;
|
|
111
|
-
const field = scene.templateId === "cinemaMedia" ? "fallbackText" : scene.templateId === "chapterTitle" ? "title" : void 0;
|
|
112
|
-
const authored = field && variables?.[field];
|
|
113
|
-
const updated = typeof authored === "string" ? copy(authored, narration) : void 0;
|
|
114
|
-
const copyChanged = typeof authored === "string" && updated !== authored;
|
|
115
|
-
if (narration === scene.narration && !copyChanged) return rawLine;
|
|
116
|
-
return JSON.stringify({ ...part, scene: { ...scene, narration, ...copyChanged ? { variables: { ...variables, [field]: updated } } : {} } });
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
59
|
// src/server/request-validation.ts
|
|
122
60
|
var MAX_GENERATED_LOOK_LENGTH = 1e3;
|
|
123
61
|
function boundedString(value, path) {
|
|
@@ -670,27 +608,38 @@ function createMediaResolvingPlanner(options) {
|
|
|
670
608
|
return false;
|
|
671
609
|
}
|
|
672
610
|
};
|
|
673
|
-
const resolveOne = (part, openingReady2) =>
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
611
|
+
const resolveOne = async (part, openingReady2) => {
|
|
612
|
+
try {
|
|
613
|
+
return await resolvePartVariables({
|
|
614
|
+
part,
|
|
615
|
+
requestId: context.request.requestId,
|
|
616
|
+
templateId: part.type === "scene.add" ? part.scene.templateId : void 0,
|
|
617
|
+
input: context.request.input,
|
|
618
|
+
signal: context.signal,
|
|
619
|
+
templates: options.templates,
|
|
620
|
+
resolveMedia: options.resolveMedia,
|
|
621
|
+
approveUrl: options.approveUrl,
|
|
622
|
+
openingReady: openingReady2 ?? options.isOpeningReady(context.request.input),
|
|
623
|
+
claimBudget,
|
|
624
|
+
onFallback: () => getGenerationLifecycleSink(context)?.reportWarning?.({
|
|
625
|
+
code: "provider_warning",
|
|
626
|
+
category: "provider",
|
|
627
|
+
message: "Some visuals use a simple background.",
|
|
628
|
+
recoverable: true
|
|
629
|
+
})
|
|
630
|
+
});
|
|
631
|
+
} catch (cause) {
|
|
632
|
+
if (context.signal.aborted || part.type !== "scene.add") throw cause;
|
|
633
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
634
|
+
if (!getGenerationLifecycleSink(context)?.rejectPart?.(error)) throw error;
|
|
635
|
+
return void 0;
|
|
636
|
+
}
|
|
637
|
+
};
|
|
691
638
|
if (limit === 1) {
|
|
692
639
|
for await (const part of options.planner(context)) {
|
|
693
|
-
if (preflight(part))
|
|
640
|
+
if (!preflight(part)) continue;
|
|
641
|
+
const resolved = await resolveOne(part);
|
|
642
|
+
if (resolved) yield resolved;
|
|
694
643
|
}
|
|
695
644
|
return;
|
|
696
645
|
}
|
|
@@ -748,7 +697,7 @@ function createMediaResolvingPlanner(options) {
|
|
|
748
697
|
pending.shift();
|
|
749
698
|
notifyProducer();
|
|
750
699
|
if ("cause" in result) throw result.cause;
|
|
751
|
-
yield result.part;
|
|
700
|
+
if (result.part) yield result.part;
|
|
752
701
|
}
|
|
753
702
|
await producer;
|
|
754
703
|
if (producerError !== void 0) throw producerError;
|
|
@@ -924,22 +873,264 @@ function createVideoHandler(options) {
|
|
|
924
873
|
});
|
|
925
874
|
}
|
|
926
875
|
|
|
876
|
+
// src/server/opening-continuity.ts
|
|
877
|
+
function continueAfterOpening(narration, earlier) {
|
|
878
|
+
const words = (value) => Array.from(value.matchAll(/[\p{L}\p{N}]+(?:['’][\p{L}\p{N}]+)*/gu));
|
|
879
|
+
let remaining = narration;
|
|
880
|
+
for (const previous of [...earlier].reverse()) {
|
|
881
|
+
const known = words(previous), current = words(remaining);
|
|
882
|
+
if (known.length < 5 || known.length > current.length) continue;
|
|
883
|
+
if (!known.every((word, index) => word[0].toLowerCase() === current[index][0].toLowerCase())) continue;
|
|
884
|
+
if (known.length === current.length) return "";
|
|
885
|
+
const last = current[known.length - 1];
|
|
886
|
+
const suffix = remaining.slice(last.index + last[0].length);
|
|
887
|
+
const boundary = suffix.match(/^[”"'’\])]*[.!?…]+[\s”"'’\])]*/u);
|
|
888
|
+
if (boundary) remaining = suffix.slice(boundary[0].length).trim();
|
|
889
|
+
}
|
|
890
|
+
return remaining;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// src/server/chat-shot-planner.ts
|
|
894
|
+
var object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
895
|
+
function text(value, maximum) {
|
|
896
|
+
return typeof value === "string" && value.trim().length <= maximum ? value.trim() : "";
|
|
897
|
+
}
|
|
898
|
+
function readShot(value) {
|
|
899
|
+
const item = object(value);
|
|
900
|
+
const narration = text(item?.narration, 2e3);
|
|
901
|
+
if (!narration) throw new Error("Chat shot requires bounded authored narration");
|
|
902
|
+
return {
|
|
903
|
+
narration,
|
|
904
|
+
subject: text(item?.subject, 80),
|
|
905
|
+
action: text(item?.action, 600),
|
|
906
|
+
durationSec: typeof item?.durationSec === "number" && Number.isFinite(item.durationSec) ? Math.min(5, Math.max(2, item.durationSec)) : 5,
|
|
907
|
+
continuity: item?.continuity === "continue" ? "continue" : "cut"
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function replaceStream(source, textStream) {
|
|
911
|
+
if (!(typeof source === "object" && source != null && "textStream" in source)) return textStream;
|
|
912
|
+
return new Proxy({ textStream }, {
|
|
913
|
+
get(target, key, receiver) {
|
|
914
|
+
return key === "textStream" ? Reflect.get(target, key, receiver) : Reflect.get(source, key, source);
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
function createChatShotPlanner(options) {
|
|
919
|
+
const incomplete = /* @__PURE__ */ new WeakSet();
|
|
920
|
+
const planner = createTextDeltaVideoPlanner({
|
|
921
|
+
includeRawProviderData: options.includeRawProviderData,
|
|
922
|
+
streamText(context) {
|
|
923
|
+
const providerContext = { ...context, userPrompt: [
|
|
924
|
+
`Create a complete answer within ${context.request.input.maxDurationSec ?? 40} seconds. Each generated clip has at most five seconds; give each spoken beat room to finish.`,
|
|
925
|
+
`Orientation: ${context.request.input.orientation ?? "landscape"}.`,
|
|
926
|
+
"USER REQUEST AND CONVERSATION",
|
|
927
|
+
context.request.input.input
|
|
928
|
+
].join("\n") };
|
|
929
|
+
const sink = getGenerationLifecycleSink(context);
|
|
930
|
+
if (sink) attachGenerationLifecycleSink(providerContext, sink);
|
|
931
|
+
let source;
|
|
932
|
+
try {
|
|
933
|
+
source = options.streamText(providerContext);
|
|
934
|
+
} catch (cause) {
|
|
935
|
+
options.publishOpening(void 0);
|
|
936
|
+
throw cause;
|
|
937
|
+
}
|
|
938
|
+
const upstream = typeof source === "object" && source != null && "textStream" in source ? source.textStream : source;
|
|
939
|
+
const translated = (async function* () {
|
|
940
|
+
let brief, buffer = "", index = 0, bodyDuration = 0, lastNarration = "";
|
|
941
|
+
let firstBody = true;
|
|
942
|
+
const reject = (cause) => {
|
|
943
|
+
incomplete.add(context);
|
|
944
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
945
|
+
if (!getGenerationLifecycleSink(context)?.rejectPart?.(error)) throw error;
|
|
946
|
+
};
|
|
947
|
+
const scenePart = (shot, closer = false) => {
|
|
948
|
+
let narration = shot.narration;
|
|
949
|
+
if (firstBody && !closer) narration = continueAfterOpening(narration, [options.openingLine ?? brief?.opening ?? ""]);
|
|
950
|
+
firstBody = false;
|
|
951
|
+
lastNarration = narration;
|
|
952
|
+
return { type: "scene.add", ...closer ? { placement: "closer" } : {}, scene: {
|
|
953
|
+
id: `${context.request.requestId}-shot-${++index}`,
|
|
954
|
+
templateId: "cinemaMedia",
|
|
955
|
+
variables: { mediaType: "video", mediaKeyword: shot.subject, shotDirection: [
|
|
956
|
+
brief?.visualDirection,
|
|
957
|
+
shot.action,
|
|
958
|
+
shot.continuity === "continue" ? "Continue the established subject, setting and action consistently." : "A deliberate new shot; choose framing that reveals this beat.",
|
|
959
|
+
"Silent illustration. No spoken dialogue, voiceover, written words or subtitles in the generated footage."
|
|
960
|
+
].filter(Boolean).join("\n") },
|
|
961
|
+
narration,
|
|
962
|
+
timing: { fixedDuration: shot.durationSec }
|
|
963
|
+
} };
|
|
964
|
+
};
|
|
965
|
+
const line = (raw) => {
|
|
966
|
+
const trimmed = raw.trim();
|
|
967
|
+
if (!trimmed || /^```(?:json|ndjson)?$/i.test(trimmed)) return;
|
|
968
|
+
const part = object(JSON.parse(trimmed));
|
|
969
|
+
if (part?.type === "answer") {
|
|
970
|
+
if (brief) throw new Error("Chat answer brief was emitted more than once");
|
|
971
|
+
brief = { opening: text(part.opening, 300), subject: text(part.subject, 80), visualDirection: text(part.visualDirection, 600), development: text(part.development, 2e3) };
|
|
972
|
+
if (part.ending) {
|
|
973
|
+
try {
|
|
974
|
+
brief.ending = readShot(part.ending);
|
|
975
|
+
} catch (cause) {
|
|
976
|
+
reject(cause);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
options.publishOpening(brief.opening ? { line: brief.opening, keyword: brief.subject } : void 0);
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
if (part?.type !== "shot") throw new Error("Chat plan requires an answer brief followed by shots");
|
|
983
|
+
if (!brief) throw new Error("Chat shot arrived before its answer brief");
|
|
984
|
+
const shot = readShot(part);
|
|
985
|
+
if (shot.narration === brief.ending?.narration) return;
|
|
986
|
+
if (firstBody && !continueAfterOpening(shot.narration, [options.openingLine ?? brief.opening])) return;
|
|
987
|
+
const budget = (context.request.input.maxDurationSec ?? 40) - (brief.ending?.durationSec ?? 5);
|
|
988
|
+
if (bodyDuration + shot.durationSec > budget) throw new Error("Chat shot exceeds the answer duration budget");
|
|
989
|
+
bodyDuration += shot.durationSec;
|
|
990
|
+
return scenePart(shot);
|
|
991
|
+
};
|
|
992
|
+
try {
|
|
993
|
+
for await (const delta of upstream) {
|
|
994
|
+
context.signal.throwIfAborted();
|
|
995
|
+
if (typeof delta !== "string") throw new Error("The LLM adapter returned a non-text delta");
|
|
996
|
+
buffer += delta;
|
|
997
|
+
if (buffer.length > 32768) throw new Error("Chat plan line exceeds the bounded stream limit");
|
|
998
|
+
let newline = buffer.indexOf("\n");
|
|
999
|
+
while (newline >= 0) {
|
|
1000
|
+
const raw = buffer.slice(0, newline);
|
|
1001
|
+
buffer = buffer.slice(newline + 1);
|
|
1002
|
+
try {
|
|
1003
|
+
const part = line(raw);
|
|
1004
|
+
if (part) yield JSON.stringify(part) + "\n";
|
|
1005
|
+
} catch (cause) {
|
|
1006
|
+
reject(cause);
|
|
1007
|
+
}
|
|
1008
|
+
newline = buffer.indexOf("\n");
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (buffer.trim()) {
|
|
1012
|
+
try {
|
|
1013
|
+
const part = line(buffer);
|
|
1014
|
+
if (part) yield JSON.stringify(part) + "\n";
|
|
1015
|
+
} catch (cause) {
|
|
1016
|
+
reject(cause);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (brief?.development && bodyDuration === 0) incomplete.add(context);
|
|
1020
|
+
if (brief?.ending && brief.ending.narration !== lastNarration) yield JSON.stringify(scenePart(brief.ending, true)) + "\n";
|
|
1021
|
+
else if (!brief?.ending) incomplete.add(context);
|
|
1022
|
+
} catch (cause) {
|
|
1023
|
+
if (!context.signal.aborted && brief?.ending && brief.ending.narration !== lastNarration) {
|
|
1024
|
+
yield JSON.stringify(scenePart(brief.ending, true)) + "\n";
|
|
1025
|
+
}
|
|
1026
|
+
throw cause;
|
|
1027
|
+
} finally {
|
|
1028
|
+
options.publishOpening(void 0);
|
|
1029
|
+
}
|
|
1030
|
+
})();
|
|
1031
|
+
return replaceStream(source, translated);
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
return async function* (context) {
|
|
1035
|
+
let completed = false;
|
|
1036
|
+
for await (const part of resolveShots(planner(context), context, options)) {
|
|
1037
|
+
if (part.type === "plan.complete") completed = true;
|
|
1038
|
+
yield part;
|
|
1039
|
+
}
|
|
1040
|
+
if (!completed) {
|
|
1041
|
+
if (incomplete.has(context)) getGenerationLifecycleSink(context)?.reportWarning?.({ code: "plan_incomplete", category: "provider", message: "Some authored answer content could not be completed.", recoverable: true });
|
|
1042
|
+
yield { type: "plan.complete", ...incomplete.has(context) ? { finishReason: "other" } : {} };
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
async function* resolveShots(parts, context, options) {
|
|
1047
|
+
const queue = [];
|
|
1048
|
+
const iterator = parts[Symbol.asyncIterator]();
|
|
1049
|
+
const limit = Number.isFinite(options.mediaConcurrency) ? Math.min(5, Math.max(1, Math.floor(options.mediaConcurrency))) : 1;
|
|
1050
|
+
let done = false, closed = false, producerError;
|
|
1051
|
+
let notify, space;
|
|
1052
|
+
const resolve = async (part) => {
|
|
1053
|
+
if (part.type !== "scene.add") return part;
|
|
1054
|
+
const { mediaKeyword } = part.scene.variables;
|
|
1055
|
+
let media;
|
|
1056
|
+
if (typeof mediaKeyword === "string" && mediaKeyword && options.resolveMedia) media = await options.resolveMedia(mediaKeyword, {
|
|
1057
|
+
input: context.request.input,
|
|
1058
|
+
requestId: context.request.requestId,
|
|
1059
|
+
scene: part.scene,
|
|
1060
|
+
templateId: "cinemaMedia",
|
|
1061
|
+
preferredType: "video",
|
|
1062
|
+
generatedLook: context.request.input.style?.generatedLook,
|
|
1063
|
+
signal: context.signal
|
|
1064
|
+
});
|
|
1065
|
+
context.signal.throwIfAborted();
|
|
1066
|
+
if (!media) getGenerationLifecycleSink(context)?.reportWarning?.({ code: "provider_warning", category: "provider", message: "Some visuals are unavailable; narration continues.", recoverable: true });
|
|
1067
|
+
const scene = { ...part.scene, variables: { mediaType: media?.type === "image" ? "photo" : "video", mediaUrl: media?.url ?? "", ...media?.posterUrl ? { mediaPoster: media.posterUrl } : {} } };
|
|
1068
|
+
return { ...part, scene };
|
|
1069
|
+
};
|
|
1070
|
+
const producer = (async () => {
|
|
1071
|
+
try {
|
|
1072
|
+
while (!closed) {
|
|
1073
|
+
if (queue.length >= limit) await new Promise((r) => {
|
|
1074
|
+
space = r;
|
|
1075
|
+
});
|
|
1076
|
+
if (closed) break;
|
|
1077
|
+
const next = await iterator.next();
|
|
1078
|
+
if (next.done) break;
|
|
1079
|
+
queue.push(resolve(next.value).then((part) => ({ part }), (error) => ({ error })));
|
|
1080
|
+
notify?.();
|
|
1081
|
+
notify = void 0;
|
|
1082
|
+
}
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
producerError = error;
|
|
1085
|
+
} finally {
|
|
1086
|
+
done = true;
|
|
1087
|
+
notify?.();
|
|
1088
|
+
notify = void 0;
|
|
1089
|
+
}
|
|
1090
|
+
})();
|
|
1091
|
+
try {
|
|
1092
|
+
while (!done || queue.length) {
|
|
1093
|
+
if (!queue.length) await new Promise((r) => {
|
|
1094
|
+
notify = r;
|
|
1095
|
+
});
|
|
1096
|
+
const item = queue[0];
|
|
1097
|
+
if (!item) continue;
|
|
1098
|
+
const result = await item;
|
|
1099
|
+
queue.shift();
|
|
1100
|
+
space?.();
|
|
1101
|
+
space = void 0;
|
|
1102
|
+
if ("error" in result) throw result.error;
|
|
1103
|
+
yield result.part;
|
|
1104
|
+
}
|
|
1105
|
+
if (producerError) throw producerError;
|
|
1106
|
+
} finally {
|
|
1107
|
+
closed = true;
|
|
1108
|
+
space?.();
|
|
1109
|
+
void iterator.return?.().catch(() => void 0);
|
|
1110
|
+
void producer;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
927
1114
|
// src/server/video-chat-prompts.ts
|
|
928
1115
|
function createVideoChatResponseInstructions(generatedVideoAvailable, openingAlreadyProvided = false, maxGeneratedVideos = 5) {
|
|
929
1116
|
return [
|
|
930
|
-
"
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
"
|
|
935
|
-
"
|
|
936
|
-
"
|
|
937
|
-
|
|
938
|
-
"
|
|
939
|
-
"
|
|
940
|
-
|
|
941
|
-
"
|
|
942
|
-
|
|
1117
|
+
"Write a complete, intentful video answer as newline-delimited JSON. Match the user's form and tone; mixed intents can combine directions.",
|
|
1118
|
+
'First write one brief: {"type":"answer","intent":"explanation|story|comedy|imagination|practical","opening":"a short inviting spoken introduction of 6\u20139 words","subject":"literal visual subject","development":"the essential development of this answer","visualDirection":"consistent subjects, appearance and visual approach","ending":{"narration":"the authored payoff","subject":"literal subject","action":"visible action or change","durationSec":5,"continuity":"cut|continue"}}.',
|
|
1119
|
+
'Then stream each developing shot on its own line: {"type":"shot","narration":"the exact spoken beat","subject":"2\u20138 literal filmable words, at most 80 characters","action":"concrete subject, action or visible change and useful framing","durationSec":5,"continuity":"cut|continue"}.',
|
|
1120
|
+
`The host permits at most ${generatedVideoAvailable ? maxGeneratedVideos : 0} generated-video attempts for this response. ${generatedVideoAvailable ? "Generated footage is preferred." : "Generated footage is unavailable."} Relevant stock is the fallback and may only depict a literal filmable subject; some requested visuals may be unavailable. Preserve the complete answer rather than shortening it to fit credits. The host selects providers; do not make source choices.`,
|
|
1121
|
+
"For a very short answer whose ending alone fulfills the request, development may be empty and no developing shots are needed. Otherwise, develop the essential content before the ending.",
|
|
1122
|
+
"The brief's ending is saved and played after your developing shots. Do not repeat it as a shot. Stop writing after the last developing shot. No technical events, identifiers, template choices, media providers, URLs or extra fields.",
|
|
1123
|
+
"Every shot uses moving footage with separate narration and subtitles. Generated footage is silent: do not ask its subjects to speak or render words. No headline cards or on-screen explanatory text.",
|
|
1124
|
+
"Each clip has at most five seconds. Write spoken beats that fit naturally, usually 8\u201310 words per five-second shot. Split longer ideas across purposeful shots, preserving facts and qualifiers. Never truncate a claim to meet a word target. Use only the shots needed within the total duration, including the ending; do not pad to a fixed count.",
|
|
1125
|
+
"Identify the full answer and its ending before developing shots. Each action must support what is said: camera movement alone is not progression. Vary scale, viewpoint and meaningful details while keeping subjects consistent.",
|
|
1126
|
+
"Explanations: clarify the actual causal mechanism, separating physical cause from a metaphor. Generated cutaways and animation illustrate ideas; they are not factual evidence. Preserve uncertainty, quantities and conditions; never invent evidence or quotations.",
|
|
1127
|
+
"Stories: portray characters making choices and experiencing consequences; use consistent character descriptions and an earned resolution, not a promised next scene.",
|
|
1128
|
+
"Comedy: establish the premise, time the visual or spoken reveal, allow a reaction beat, and stop on the payoff without explaining the joke.",
|
|
1129
|
+
"Imagination: make the impossible action concrete, establish the world's internal rules and keep its imagery consistent. Do not replace imagination with an explanation of it.",
|
|
1130
|
+
"Practical answers: show usable actions in their necessary order, with framing that makes the method and result visible. Preserve essential steps and relevant safety conditions.",
|
|
1131
|
+
openingAlreadyProvided ? "The supplied opening has already been spoken. Preserve it and begin the body with new content." : "The brief opening is spoken during preparation. The first body shot must develop it rather than repeat its words or claim.",
|
|
1132
|
+
"Use continuity=continue when the same subject/action should remain coherent; choose cut for a purposeful new view. Describe recurring subjects consistently. Never assume a different angle or generated depiction proves a factual claim."
|
|
1133
|
+
].join("\n");
|
|
943
1134
|
}
|
|
944
1135
|
var VIDEO_CHAT_NARRATION_PROMPT = [
|
|
945
1136
|
"You narrate a short video response, one scene at a time.",
|
|
@@ -983,9 +1174,7 @@ var DEFAULT_MAX_AUDIO_BYTES = 8 * 1024 * 1024;
|
|
|
983
1174
|
var MAX_PROMPT_CHARACTERS = 8e3;
|
|
984
1175
|
var MAX_CONVERSATION_TURNS = 12;
|
|
985
1176
|
var MAX_CONVERSATION_RESPONSE_CHARACTERS = 8e3;
|
|
986
|
-
var VIDEO_CHAT_OPENING_PLAN_TYPE = "video-chat.opening";
|
|
987
1177
|
var VIDEO_CHAT_OPENING_EVENT_TYPE = "data.video-chat-opening";
|
|
988
|
-
var PROVIDER_CODE_FENCE = /^```(?:json|ndjson)?$/i;
|
|
989
1178
|
var DEFAULT_WELCOME_PROMPTS = [
|
|
990
1179
|
{
|
|
991
1180
|
prompt: "Why does the Moon always show one face?",
|
|
@@ -1096,64 +1285,6 @@ async function readJson(request, maximum) {
|
|
|
1096
1285
|
function cleanGeneratedText(value) {
|
|
1097
1286
|
return value.trim().replace(/^["']|["']$/g, "");
|
|
1098
1287
|
}
|
|
1099
|
-
function readGeneratedFirstShot(value) {
|
|
1100
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1101
|
-
const shot = value;
|
|
1102
|
-
const bounded = (field, maximum) => typeof field === "string" ? [...field.trim()].slice(0, maximum).join("").trim() : "";
|
|
1103
|
-
const text = bounded(shot.text, 65);
|
|
1104
|
-
const narration = bounded(shot.narration, 300);
|
|
1105
|
-
const mediaKeyword = bounded(shot.mediaKeyword, 80).match(/\S+/gu)?.slice(0, 8).join(" ") ?? "";
|
|
1106
|
-
const shotDirection = bounded(shot.shotDirection, 220);
|
|
1107
|
-
return text && narration && mediaKeyword ? { text, narration, mediaKeyword, ...shotDirection ? { shotDirection } : {} } : void 0;
|
|
1108
|
-
}
|
|
1109
|
-
function boundedWords(value, maximum, characters) {
|
|
1110
|
-
if (typeof value !== "string") return "";
|
|
1111
|
-
return value.trim().match(/\S+/gu)?.slice(0, maximum).join(" ").slice(0, characters).trim() ?? "";
|
|
1112
|
-
}
|
|
1113
|
-
function readOpeningPlanLine(line) {
|
|
1114
|
-
try {
|
|
1115
|
-
const parsed = JSON.parse(line);
|
|
1116
|
-
if (!parsed || parsed.type !== VIDEO_CHAT_OPENING_PLAN_TYPE) return void 0;
|
|
1117
|
-
const firstShot = readGeneratedFirstShot(parsed.firstShot);
|
|
1118
|
-
return {
|
|
1119
|
-
line: boundedWords(parsed.spokenHook, 9, 300),
|
|
1120
|
-
keyword: boundedWords(parsed.mediaKeyword, 4, 80),
|
|
1121
|
-
...boundedWords(parsed.fallbackKeyword, 4, 80) ? { fallbackKeyword: boundedWords(parsed.fallbackKeyword, 4, 80) } : {},
|
|
1122
|
-
...firstShot ? { firstShot } : {}
|
|
1123
|
-
};
|
|
1124
|
-
} catch {
|
|
1125
|
-
return void 0;
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
function reservedFirstScene(requestId, firstShot) {
|
|
1129
|
-
return {
|
|
1130
|
-
type: "scene.add",
|
|
1131
|
-
scene: {
|
|
1132
|
-
id: `${requestId}-first-shot`,
|
|
1133
|
-
templateId: "cinemaMedia",
|
|
1134
|
-
variables: {
|
|
1135
|
-
fallbackText: firstShot.text,
|
|
1136
|
-
mediaSource: "generate",
|
|
1137
|
-
...firstShot.shotDirection ? { shotDirection: firstShot.shotDirection } : {},
|
|
1138
|
-
mediaType: "video",
|
|
1139
|
-
mediaKeyword: firstShot.mediaKeyword
|
|
1140
|
-
},
|
|
1141
|
-
timing: { fixedDuration: 5 },
|
|
1142
|
-
narration: firstShot.narration
|
|
1143
|
-
}
|
|
1144
|
-
};
|
|
1145
|
-
}
|
|
1146
|
-
function replaceTextStream(source, textStream) {
|
|
1147
|
-
const enriched = typeof source === "object" && source != null && "textStream" in source ? source : void 0;
|
|
1148
|
-
if (!enriched) return textStream;
|
|
1149
|
-
const wrapper = { textStream };
|
|
1150
|
-
return new Proxy(wrapper, {
|
|
1151
|
-
get(target, property, receiver) {
|
|
1152
|
-
if (property === "textStream") return Reflect.get(target, property, receiver);
|
|
1153
|
-
return Reflect.get(enriched, property, enriched);
|
|
1154
|
-
}
|
|
1155
|
-
});
|
|
1156
|
-
}
|
|
1157
1288
|
function createOpeningChannel(initial) {
|
|
1158
1289
|
if (initial) return { ready: Promise.resolve(initial), publish: () => void 0 };
|
|
1159
1290
|
let published = false;
|
|
@@ -1170,77 +1301,6 @@ function createOpeningChannel(initial) {
|
|
|
1170
1301
|
}
|
|
1171
1302
|
};
|
|
1172
1303
|
}
|
|
1173
|
-
function interceptOpeningPlan(source, options) {
|
|
1174
|
-
const enriched = typeof source === "object" && source != null && "textStream" in source ? source : void 0;
|
|
1175
|
-
const upstream = enriched?.textStream ?? source;
|
|
1176
|
-
const textStream = (async function* () {
|
|
1177
|
-
let buffer = "";
|
|
1178
|
-
let decided = !options.expectOpening;
|
|
1179
|
-
const continuation = createOpeningContinuation(options.openingLine);
|
|
1180
|
-
const acceptOpening = (opening) => {
|
|
1181
|
-
if (!options.openingProvided) {
|
|
1182
|
-
options.publish(opening.line ? opening : void 0);
|
|
1183
|
-
continuation.remember(opening.line);
|
|
1184
|
-
}
|
|
1185
|
-
if (!options.generatedVideoAvailable || !opening.firstShot) return void 0;
|
|
1186
|
-
const narration = continuation.narration(opening.firstShot.narration);
|
|
1187
|
-
if (!narration) return void 0;
|
|
1188
|
-
const text = continuation.copy(opening.firstShot.text, narration);
|
|
1189
|
-
continuation.remember(narration);
|
|
1190
|
-
return JSON.stringify(reservedFirstScene(options.requestId, { ...opening.firstShot, text, narration }));
|
|
1191
|
-
};
|
|
1192
|
-
try {
|
|
1193
|
-
for await (const delta of upstream) {
|
|
1194
|
-
if (typeof delta !== "string") throw new Error("The LLM adapter returned a non-text delta");
|
|
1195
|
-
buffer += delta;
|
|
1196
|
-
let newline = buffer.indexOf("\n");
|
|
1197
|
-
while (newline >= 0) {
|
|
1198
|
-
const rawLine = buffer.slice(0, newline);
|
|
1199
|
-
buffer = buffer.slice(newline + 1);
|
|
1200
|
-
const line = rawLine.trim();
|
|
1201
|
-
if (!decided && line && !PROVIDER_CODE_FENCE.test(line)) {
|
|
1202
|
-
const opening = readOpeningPlanLine(line);
|
|
1203
|
-
decided = true;
|
|
1204
|
-
if (opening) {
|
|
1205
|
-
const firstScene = acceptOpening(opening);
|
|
1206
|
-
if (firstScene) yield `${firstScene}
|
|
1207
|
-
`;
|
|
1208
|
-
newline = buffer.indexOf("\n");
|
|
1209
|
-
continue;
|
|
1210
|
-
}
|
|
1211
|
-
options.publish(void 0);
|
|
1212
|
-
}
|
|
1213
|
-
const continued = continuation.line(rawLine);
|
|
1214
|
-
if (continued != null) yield `${continued}
|
|
1215
|
-
`;
|
|
1216
|
-
newline = buffer.indexOf("\n");
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
if (buffer) {
|
|
1220
|
-
const line = buffer.trim();
|
|
1221
|
-
if (!decided && line && !PROVIDER_CODE_FENCE.test(line)) {
|
|
1222
|
-
const opening = readOpeningPlanLine(line);
|
|
1223
|
-
decided = true;
|
|
1224
|
-
if (opening) {
|
|
1225
|
-
const firstScene = acceptOpening(opening);
|
|
1226
|
-
if (firstScene) yield `${firstScene}
|
|
1227
|
-
`;
|
|
1228
|
-
} else {
|
|
1229
|
-
options.publish(void 0);
|
|
1230
|
-
const continued = continuation.line(buffer);
|
|
1231
|
-
if (continued != null) yield continued;
|
|
1232
|
-
}
|
|
1233
|
-
} else {
|
|
1234
|
-
const continued = continuation.line(buffer);
|
|
1235
|
-
if (continued != null) yield continued;
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
} finally {
|
|
1239
|
-
options.publish(void 0);
|
|
1240
|
-
}
|
|
1241
|
-
})();
|
|
1242
|
-
return replaceTextStream(source, textStream);
|
|
1243
|
-
}
|
|
1244
1304
|
function resequenceEvent(event, sequence) {
|
|
1245
1305
|
return {
|
|
1246
1306
|
...event,
|
|
@@ -1326,11 +1386,11 @@ function streamVideoChatOpening(response, openingReady) {
|
|
|
1326
1386
|
headers: response.headers
|
|
1327
1387
|
});
|
|
1328
1388
|
}
|
|
1329
|
-
function readSuggestionSubjects(
|
|
1330
|
-
const opening =
|
|
1331
|
-
const closing =
|
|
1389
|
+
function readSuggestionSubjects(text2) {
|
|
1390
|
+
const opening = text2.indexOf("{");
|
|
1391
|
+
const closing = text2.lastIndexOf("}");
|
|
1332
1392
|
if (opening < 0 || closing <= opening) return [];
|
|
1333
|
-
const parsed = JSON.parse(
|
|
1393
|
+
const parsed = JSON.parse(text2.slice(opening, closing + 1));
|
|
1334
1394
|
return (Array.isArray(parsed.suggestions) ? parsed.suggestions : []).flatMap((entry) => {
|
|
1335
1395
|
if (!entry || typeof entry !== "object") return [];
|
|
1336
1396
|
const item = entry;
|
|
@@ -1413,14 +1473,7 @@ function createVideoChatHandler(options) {
|
|
|
1413
1473
|
const generatedVideoAvailable = generateVideo != null && maxGeneratedVideos > 0;
|
|
1414
1474
|
let lifecycle;
|
|
1415
1475
|
let generatedAttempts = 0;
|
|
1416
|
-
const completedVideos = /* @__PURE__ */ new Map();
|
|
1417
|
-
const reusedUrls = /* @__PURE__ */ new Set();
|
|
1418
1476
|
const resolveSelected = generateVideo || searchMedia ? async (query, context) => {
|
|
1419
|
-
const reuseKey = JSON.stringify([query, context.generatedLook ?? "", context.scene.variables.shotDirection ?? "", context.input.orientation ?? "landscape"]);
|
|
1420
|
-
const remember = (media) => {
|
|
1421
|
-
if (media?.type === "video") completedVideos.set(reuseKey, media);
|
|
1422
|
-
return media;
|
|
1423
|
-
};
|
|
1424
1477
|
const mediaContext = {
|
|
1425
1478
|
purpose: "response",
|
|
1426
1479
|
orientation: context.input.orientation ?? "landscape",
|
|
@@ -1448,9 +1501,9 @@ function createVideoChatHandler(options) {
|
|
|
1448
1501
|
return null;
|
|
1449
1502
|
}
|
|
1450
1503
|
};
|
|
1451
|
-
if (generatedVideoAvailable && context.scene.variables.mediaSource === "generate") {
|
|
1504
|
+
if (generatedVideoAvailable && (!options.templates || context.scene.variables.mediaSource === "generate")) {
|
|
1452
1505
|
const generated = generatedAttempts < maxGeneratedVideos ? (generatedAttempts++, await attempt(generateVideo, generateVideoTimeoutMs)) : null;
|
|
1453
|
-
if (generated) return
|
|
1506
|
+
if (generated?.type === "video") return generated;
|
|
1454
1507
|
lifecycle?.reportWarning?.({
|
|
1455
1508
|
code: "provider_warning",
|
|
1456
1509
|
category: "provider",
|
|
@@ -1459,42 +1512,46 @@ function createVideoChatHandler(options) {
|
|
|
1459
1512
|
});
|
|
1460
1513
|
}
|
|
1461
1514
|
const stock = await attempt(searchMedia, 3e3);
|
|
1462
|
-
if (stock) return
|
|
1463
|
-
if (generatedVideoAvailable) {
|
|
1464
|
-
const previous = completedVideos.get(reuseKey);
|
|
1465
|
-
if (previous && !reusedUrls.has(previous.url)) {
|
|
1466
|
-
reusedUrls.add(previous.url);
|
|
1467
|
-
return previous;
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1515
|
+
if (stock && (options.templates || stock.type === "video")) return stock;
|
|
1470
1516
|
return null;
|
|
1471
1517
|
} : void 0;
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1518
|
+
if (options.templates) {
|
|
1519
|
+
openingChannel.publish(void 0);
|
|
1520
|
+
return createVideoHandler({
|
|
1521
|
+
...videoOptions,
|
|
1522
|
+
authorize: "none",
|
|
1523
|
+
allowedOrigins,
|
|
1524
|
+
allowCredentials,
|
|
1525
|
+
maxBodyBytes,
|
|
1526
|
+
mediaConcurrency,
|
|
1527
|
+
resolveMedia: resolveSelected,
|
|
1528
|
+
narrate: true,
|
|
1529
|
+
basePrompt: instructions
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
const handler = createVideoStreamHandler({
|
|
1533
|
+
heartbeatMs: videoOptions.heartbeatMs,
|
|
1534
|
+
onError: videoOptions.onError,
|
|
1535
|
+
onWarning: videoOptions.onWarning,
|
|
1536
|
+
onComplete: videoOptions.onComplete,
|
|
1537
|
+
invalidPartBehavior: videoOptions.invalidPartBehavior,
|
|
1538
|
+
requireCloser: options.requireCloser ?? true,
|
|
1539
|
+
generate: createChatShotPlanner({
|
|
1540
|
+
streamText: (context) => {
|
|
1541
|
+
lifecycle = getGenerationLifecycleSink(context);
|
|
1542
|
+
return videoOptions.streamText(context);
|
|
1543
|
+
},
|
|
1544
|
+
includeRawProviderData: videoOptions.includeRawProviderData,
|
|
1545
|
+
openingLine,
|
|
1546
|
+
publishOpening: openingChannel.publish,
|
|
1547
|
+
resolveMedia: resolveSelected,
|
|
1548
|
+
mediaConcurrency
|
|
1549
|
+
}),
|
|
1490
1550
|
authorize: "none",
|
|
1491
1551
|
allowedOrigins,
|
|
1492
1552
|
allowCredentials,
|
|
1493
1553
|
maxBodyBytes,
|
|
1494
|
-
|
|
1495
|
-
basePrompt: [createVideoChatResponseInstructions(generatedVideoAvailable, openingProvided, maxGeneratedVideos), instructions?.trim()].filter(Boolean).join("\n\nAPPLICATION GUIDANCE\n"),
|
|
1496
|
-
narrate: true,
|
|
1497
|
-
resolveMedia: resolveSelected
|
|
1554
|
+
systemPrompt: [createVideoChatResponseInstructions(generatedVideoAvailable, openingProvided, maxGeneratedVideos), instructions?.trim()].filter(Boolean).join("\n\nAPPLICATION GUIDANCE\n")
|
|
1498
1555
|
});
|
|
1499
1556
|
return handler;
|
|
1500
1557
|
};
|
|
@@ -1589,12 +1646,12 @@ function createVideoChatHandler(options) {
|
|
|
1589
1646
|
if (audio.byteLength === 0) return jsonError2(400, "empty_audio", "No audio was provided", headers);
|
|
1590
1647
|
if (audio.byteLength > maxAudioBytes) return jsonError2(413, "body_too_large", "The recording is too large", headers);
|
|
1591
1648
|
try {
|
|
1592
|
-
const
|
|
1649
|
+
const text2 = await transcribe({
|
|
1593
1650
|
audio,
|
|
1594
1651
|
mediaType: request.headers.get("content-type") || "audio/webm",
|
|
1595
1652
|
signal: request.signal
|
|
1596
1653
|
});
|
|
1597
|
-
return Response.json({ text:
|
|
1654
|
+
return Response.json({ text: text2.trim() }, { headers });
|
|
1598
1655
|
} catch (cause) {
|
|
1599
1656
|
reportError(cause);
|
|
1600
1657
|
return jsonError2(502, "transcription_failed", "The recording could not be transcribed", headers);
|
|
@@ -1700,7 +1757,7 @@ function createVideoChatHandler(options) {
|
|
|
1700
1757
|
const prompt = boundedString2(value.prompt, "request.prompt");
|
|
1701
1758
|
const lines = Array.isArray(value.lines) ? value.lines.slice(-8).map((line, index) => boundedString2(line, `request.lines[${index}]`, 2e3)) : [];
|
|
1702
1759
|
try {
|
|
1703
|
-
const
|
|
1760
|
+
const text2 = await withDeadline((signal) => callText(
|
|
1704
1761
|
"suggestions",
|
|
1705
1762
|
VIDEO_CHAT_SUGGESTIONS_PROMPT,
|
|
1706
1763
|
`USER PROMPT: ${prompt}
|
|
@@ -1710,7 +1767,7 @@ ${lines.join("\n")}`,
|
|
|
1710
1767
|
512,
|
|
1711
1768
|
signal
|
|
1712
1769
|
), 3e3, request.signal);
|
|
1713
|
-
const subjects = readSuggestionSubjects(
|
|
1770
|
+
const subjects = readSuggestionSubjects(text2);
|
|
1714
1771
|
const mediaResolver = searchMedia;
|
|
1715
1772
|
const media = await Promise.all(subjects.map(async (subject) => {
|
|
1716
1773
|
if (!mediaResolver || !subject.keyword) return null;
|
|
@@ -1741,9 +1798,9 @@ ${lines.join("\n")}`,
|
|
|
1741
1798
|
if (!generateSpeech) return new Response(null, { status: 204, headers });
|
|
1742
1799
|
const value = record2(body, "request");
|
|
1743
1800
|
allowedKeys2(value, ["text"], "request");
|
|
1744
|
-
const
|
|
1801
|
+
const text2 = boundedString2(value.text, "request.text", 4e3);
|
|
1745
1802
|
try {
|
|
1746
|
-
const result = await withDeadline((signal) => generateSpeech({ text, signal }), 3e3, request.signal);
|
|
1803
|
+
const result = await withDeadline((signal) => generateSpeech({ text: text2, signal }), 3e3, request.signal);
|
|
1747
1804
|
return new Response(audioBody(result.audio), {
|
|
1748
1805
|
headers: {
|
|
1749
1806
|
...Object.fromEntries(headers),
|