ventures: refresh havre from havre-next
This commit is contained in:
1 parent
32268f7a28
commit
b1328774a3
7 files changed
+142
-18
No files matched your search
@@ -403,6 +403,24 @@ export function underage(texts, re = UNDERAGE) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Words a content filter (Venice's, or the model maker's) may weigh in a picture prompt, for the "what venice saw" block
|
||||
// under a picture that was turned down: Havre's own under-18 words, and the softer ones about age and youth that pass its
|
||||
// check (it goes by apparent age; a filter reads "young", "girl" or an age as it likes). Venice's stock line never says
|
||||
// which words it read, so these are the ones to look at first. Ages are any two-figure number written as one.
|
||||
const FILTER = new RegExp("\\b(?:" + [...YOUNG, ...CHILDREN, "young\\w*", "youth\\w*", "girls?", "girlish", "girly", "boys?", "boyish",
|
||||
"students?", "petite", "bab(?:y|ies)", "daughters?", "sons?", "nieces?", "nephews?", "pupils?", "lolita", "barely legal",
|
||||
"fresh(?:man|men)", "sophomores?", "\\d{1,2}(?=\\s*(?:\\+|or older|and up|years?|yrs?|y\\.?o\\b))"].join("|") + ")\\b", "gi");
|
||||
/** the words in a prompt a content filter may weigh (FILTER), each once, in order, at most eight */
|
||||
export function filterWords(text) {
|
||||
const out = [];
|
||||
for (const m of str(text, 200000).matchAll(FILTER)) {
|
||||
const w = m[0].toLowerCase();
|
||||
if (!out.includes(w)) out.push(w);
|
||||
if (out.length >= 8) break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** everything a card says about them */
|
||||
const cardWords = (card) => [card.name, card.tagline, card.greeting, card.persona, card.voice && card.voice.notes,
|
||||
card.look && card.look.description, ...(card.tags || []), ...(card.examples || []).flatMap((e) => [e.user, e.character]),
|
||||
|
||||
+28
-8
@@ -1545,7 +1545,7 @@ function askFor(kind) {
|
||||
if (hint) { $("#say").value = ""; growSay(); }
|
||||
// refused: the photo models that turned this photo down (each gets the next one a click away)
|
||||
S.ask = { kind, card: c.id, thread: S.open, hint, shot: "", busy: false, status: "", bad: false, quote: null, quoting: false, opts: null, asked: 0,
|
||||
refused: [] };
|
||||
refused: [], detail: null };
|
||||
loadList(kind === "clip" ? "video" : "inpaint");
|
||||
if (!refsOf(c).length) loadList("image");
|
||||
renderAsk();
|
||||
@@ -1645,6 +1645,7 @@ function renderAsk() {
|
||||
: `safe for work, and never a real person. a ${what} runs on your venice key.` }));
|
||||
if (ad.why) box.append(h("p", { class: "mono", id: "ask-why", text: ad.why }));
|
||||
if (a.status) box.append(h("p", { class: "status" + (a.bad ? " bad" : ""), role: "status", text: a.status }));
|
||||
if (a.status && a.bad && a.detail) box.append(sawBlock(a.detail));
|
||||
syncAsk();
|
||||
if (was) { const t = $("#ask-shot"); if (t && !t.disabled) { t.focus({ preventScroll: true }); t.setSelectionRange(was[0], was[1]); } }
|
||||
else if (focusId) { const el = document.getElementById(focusId); if (el && !el.disabled) el.focus({ preventScroll: true }); }
|
||||
@@ -1696,7 +1697,7 @@ async function writeShot() {
|
||||
const c = S.cards.find((x) => x.id === a.card);
|
||||
if (!c) return;
|
||||
a.busy = true;
|
||||
Object.assign(a, { status: "writing the shot…", bad: false });
|
||||
Object.assign(a, { status: "writing the shot…", bad: false, detail: null });
|
||||
renderAsk();
|
||||
try {
|
||||
const t = askChat(a), adult = adultFor(c, t, a.hint).ok;
|
||||
@@ -1740,6 +1741,25 @@ function trouble(e, c) {
|
||||
return e.status === 400 && c.engine.venice_character && !c.look.references.length
|
||||
? `Venice couldn't use ${c.name}'s photo (${e.message}). Add a picture of them on their card (card → look) and try again.` : e.message;
|
||||
}
|
||||
/** what was sent to Venice and what it answered, for the block under a failure (venice.js sent); null when it never got that far */
|
||||
const saw = (e, m) => (e && e.sent ? { ...e.sent, name: (m && m.name) || e.sent.model, status: e.status, code: e.code || "", said: e.said || "", body: e.body || "" } : null);
|
||||
/**
|
||||
* Under a photo or a clip that didn't come: the model and the call, Venice's answer as it came, the words in the prompt
|
||||
* a content filter may weigh (abilities.js filterWords), and the prompt itself, to read or copy. Venice's stock line
|
||||
* ("violates the content policy of Venice.ai or the model provider") never says which words, so this is how to find them.
|
||||
*/
|
||||
function sawBlock(d) {
|
||||
const words = A.filterWords(d.prompt), n = d.pictures || 0;
|
||||
const call = [d.name === d.model ? d.model : `${d.name} (${d.model})`, d.endpoint, n ? `${n} picture${n === 1 ? "" : "s"}` : "",
|
||||
d.safe_mode == null ? "" : `safe mode ${d.safe_mode ? "on" : "off"}`].filter(Boolean).join(" · ");
|
||||
const answer = d.said || (d.body && d.body !== "{}" ? d.body : "") || "nothing more";
|
||||
const text = [call, `venice: ${d.status || "no answer"}${d.code ? " " + d.code : ""} — ${answer}`,
|
||||
`words a filter may weigh (ages, youth): ${words.length ? words.map((w) => `“${w}”`).join(", ") : "none havre knows of"}. a real person's name, or a public figure's, is turned down too.`,
|
||||
"prompt:", String(d.prompt || "")].join("\n");
|
||||
const copy = h("button", { type: "button", class: "btn small", text: "COPY" });
|
||||
copy.addEventListener("click", () => copyFrom("ask-saw", copy));
|
||||
return h("details", { class: "saw" }, h("summary", { text: "what venice saw" }), h("pre", { class: "saw", id: "ask-saw", text }), h("div", { class: "acts" }, copy));
|
||||
}
|
||||
async function takePhoto() {
|
||||
const a = S.ask;
|
||||
if (!a || a.busy || !a.shot.trim()) return;
|
||||
@@ -1753,7 +1773,7 @@ async function takePhoto() {
|
||||
const refs = refsOf(c).slice(0, Math.max(1, pm.maxIn)), price = A.photoPrice(pm, refs.length);
|
||||
if (price == null || !refs.length) return; // never without the price on the button
|
||||
a.busy = true;
|
||||
Object.assign(a, { status: `taking the photo with ${pm.name}…`, bad: false });
|
||||
Object.assign(a, { status: `taking the photo with ${pm.name}…`, bad: false, detail: null });
|
||||
renderAsk();
|
||||
try {
|
||||
const { blob, blurred } = await new Venice(S.key).editImage({ model: pm.id, prompt: A.photoPrompt(c, a.shot, refs.length, pm.promptLimit, { adult }),
|
||||
@@ -1766,7 +1786,7 @@ async function takePhoto() {
|
||||
} catch (e) {
|
||||
a.busy = false;
|
||||
if (e.refused && !a.refused.includes(pm.id)) a.refused.push(pm.id); // the next model is a click away (askAlt)
|
||||
Object.assign(a, { status: trouble(e, c), bad: true });
|
||||
Object.assign(a, { status: trouble(e, c), bad: true, detail: saw(e, pm) }); // and what Venice saw is a click away (sawBlock)
|
||||
renderAsk();
|
||||
}
|
||||
balanceSoon();
|
||||
@@ -1781,7 +1801,7 @@ async function firstPortrait() {
|
||||
if (!c || !pm || pm.price == null) return;
|
||||
if (refsOf(c).length) { renderAsk(); writeShot(); return; } // they've had a picture since: no portrait to pay for
|
||||
a.busy = true;
|
||||
Object.assign(a, { status: c.look.description ? `drawing ${c.name} with ${pm.name}…` : "imagining how they look…", bad: false });
|
||||
Object.assign(a, { status: c.look.description ? `drawing ${c.name} with ${pm.name}…` : "imagining how they look…", bad: false, detail: null });
|
||||
renderAsk();
|
||||
try {
|
||||
const v = new Venice(S.key), look = await lookOf(c, v);
|
||||
@@ -1797,7 +1817,7 @@ async function firstPortrait() {
|
||||
writeShot();
|
||||
} catch (e) {
|
||||
a.busy = false;
|
||||
Object.assign(a, { status: redraw(e), bad: true });
|
||||
Object.assign(a, { status: redraw(e), bad: true, detail: saw(e, pm) });
|
||||
renderAsk();
|
||||
}
|
||||
balanceSoon();
|
||||
@@ -1984,7 +2004,7 @@ async function makeClip() {
|
||||
const cm = askClipModel(a);
|
||||
if (!cm || !quoteFits(a)) return; // only at the price on the button
|
||||
a.busy = true;
|
||||
Object.assign(a, { status: "asking venice to start the clip…", bad: false });
|
||||
Object.assign(a, { status: "asking venice to start the clip…", bad: false, detail: null });
|
||||
renderAsk();
|
||||
try {
|
||||
await startClip(c, cm, a.shot.trim(), { ...a.opts }, a.quote, () => askThread(a), { adult });
|
||||
@@ -1992,7 +2012,7 @@ async function makeClip() {
|
||||
renderAsk();
|
||||
} catch (e) {
|
||||
a.busy = false;
|
||||
Object.assign(a, { status: trouble(e, c), bad: true });
|
||||
Object.assign(a, { status: trouble(e, c), bad: true, detail: saw(e, cm) });
|
||||
renderAsk();
|
||||
}
|
||||
balanceSoon();
|
||||
|
||||
@@ -155,8 +155,12 @@ them only from pictures the person can see on the card.
|
||||
Word those rules for what's wanted, never by naming what they rule out: content filters (Venice's, and the ones of
|
||||
the models' makers) read the words, not the "no" in front of them, so a rule that names what it forbids gets a
|
||||
harmless prompt turned down. When Venice turns one down anyway (a 422 its docs call `CONTENT_POLICY_VIOLATION`;
|
||||
`VeniceError.refused`), offer the next model (`A.another(list, tried, kind)`) instead of a dead end. A photo a
|
||||
character starts on their own tries one other model, then says in the chat that it didn't come.
|
||||
`VeniceError.refused`), offer the next model (`A.another(list, tried, kind)`) instead of a dead end, and show what
|
||||
was sent and what came back: the error carries Venice's answer as it came (`said`, `code`, `body`) and the call
|
||||
(`sent`: endpoint, model, pictures, safe mode, prompt), and `A.filterWords(prompt)` lists the words in the prompt a
|
||||
filter may weigh (ages, youth), since Venice's stock line never says which. Havre's photo panel shows all of it
|
||||
under "what venice saw". A photo a character starts on their own tries one other model, then says in the chat that
|
||||
it didn't come.
|
||||
|
||||
The one exception is an `adult` character, once the person has said they're 18 or older: when every picture on their
|
||||
card was made with AI (never one that was uploaded, which could be of anyone), their photos and clips can be adult.
|
||||
|
||||
@@ -258,6 +258,14 @@ input:disabled, textarea:disabled, select:disabled { background: #f2f2f2; color:
|
||||
.ask .opts label { flex-direction: row; align-items: center; gap: 6px; }
|
||||
.ask .opts select { width: auto; min-height: 34px; font: 13px var(--mono); }
|
||||
.ask .acts { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
/* what venice saw: under a photo or clip that was turned down, the call, the answer and the prompt, to read or copy */
|
||||
.ask details.saw { border: 1px dashed var(--ink); padding: 0 10px; }
|
||||
.ask details.saw summary { padding: 8px 0; font: 13px var(--mono); cursor: pointer; }
|
||||
.ask details.saw .acts { padding: 0 0 10px; }
|
||||
pre.saw {
|
||||
margin: 0 0 10px; padding: 8px 10px; border: 1px solid var(--ink); max-height: 40vh; overflow: auto;
|
||||
white-space: pre-wrap; overflow-wrap: anywhere; font: 13px/1.45 var(--mono);
|
||||
}
|
||||
|
||||
/* photos, clips and voice notes in the conversation */
|
||||
.line.media .said { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; white-space: normal; }
|
||||
|
||||
@@ -432,6 +432,48 @@ test("venice: what Venice says when it turns something down, and when a 422 is s
|
||||
await assert.rejects(v.speak({ model: "m", input: "x" }), said(/busy at Venice/, false, 503));
|
||||
});
|
||||
|
||||
test("venice: an error carries Venice's answer as it came, and a picture or clip call carries what it sent", async () => {
|
||||
const answers = {
|
||||
"/image/edit": { status: 422, body: { error: { code: "CONTENT_POLICY_VIOLATION", message: "Your prompt violates the content policy of Venice.ai or the model provider" } } },
|
||||
"/image/multi-edit": { status: 422, body: {} },
|
||||
"/image/generate": { status: 400, body: { error: "Invalid request parameters", details: { prompt: "too long" } } },
|
||||
"/video/queue": { status: 422, body: { error: "Your prompt violates the content policy of Venice.ai or the model provider" } },
|
||||
"/audio/speech": { status: 503, body: { error: "The model is at capacity." } },
|
||||
};
|
||||
const v = new Venice("sk", { fetch: fake((p) => answers[p] || { status: 404, body: {} }).f });
|
||||
const caught = async (p) => { try { await p; } catch (e) { return e; } assert.fail("no error"); };
|
||||
let e = await caught(v.editImage({ model: "qwen-edit-uncensored", prompt: "Hard rule. The reference picture shows Mara.", images: ["data:image/png;base64,AA"], safe: false }));
|
||||
assert.deepEqual([e.said, e.code, e.refused], ["Your prompt violates the content policy of Venice.ai or the model provider", "CONTENT_POLICY_VIOLATION", true]);
|
||||
assert.deepEqual(e.sent, { endpoint: "image/edit", model: "qwen-edit-uncensored", pictures: 1, safe_mode: false, prompt: "Hard rule. The reference picture shows Mara." });
|
||||
e = await caught(v.editImage({ model: "m", prompt: "p", images: ["a", "b", "c"] }));
|
||||
assert.deepEqual([e.said, e.code, e.body], ["", "", "{}"], "nothing said: the body as it came");
|
||||
assert.deepEqual(e.sent, { endpoint: "image/multi-edit", model: "m", pictures: 3, safe_mode: true, prompt: "p" });
|
||||
e = await caught(v.generateImage({ model: "g", prompt: "Portrait photo of Wren." }));
|
||||
assert.deepEqual([e.status, e.refused, e.said, e.code], [400, false, "Invalid request parameters", ""]);
|
||||
assert.match(e.body, /"details":\{"prompt":"too long"\}/, "the body keeps what the message leaves out");
|
||||
assert.deepEqual(e.sent, { endpoint: "image/generate", model: "g", pictures: 0, safe_mode: true, prompt: "Portrait photo of Wren." });
|
||||
e = await caught(v.queueVideo({ model: "wan-3-0-reference-to-video", prompt: "She waves.", duration: "5s", images: ["a", "b", "c", "d", "e"] }));
|
||||
assert.deepEqual(e.sent, { endpoint: "video/queue", model: "wan-3-0-reference-to-video", pictures: 4, prompt: "She waves." }, "a clip: as many pictures as go");
|
||||
assert.equal(e.refused, true);
|
||||
e = await caught(v.speak({ model: "m", input: "x" }));
|
||||
assert.deepEqual([e.status, e.said, e.sent], [503, "The model is at capacity.", undefined], "a voice note sends no picture prompt");
|
||||
e = await caught(v.editImage({ model: "m", prompt: "p", images: [] }));
|
||||
assert.equal(e.sent, undefined, "nothing was sent");
|
||||
});
|
||||
|
||||
test("the words in a prompt a content filter may weigh, for what venice saw", () => {
|
||||
assert.deepEqual(A.filterWords(A.photoPrompt(mara, "On the stage, in a navy coat.", 1, 1500, { adult: true })), ["18"], "the 18+ rule's own age, once");
|
||||
assert.deepEqual(A.filterWords(A.photoPrompt(ines, "On deck.", 1, 1500)), [], "a safe-for-work photo: none");
|
||||
assert.deepEqual(A.filterWords("A young woman in her early twenties, a schoolgirl's satchel, 21 years old, with her little sister, a girl of nine, and their son."),
|
||||
["young", "schoolgirl", "21", "girl", "son"]);
|
||||
assert.deepEqual(A.filterWords("Her boyfriend, a person at a girlish party, babies everywhere, 2 students"), ["girlish", "babies", "students"], "boyfriend and person aren't boy or son");
|
||||
assert.deepEqual(A.filterWords("18 or older; 25+; 3 yo; a teen"), ["18", "25", "3 yo", "teen"]);
|
||||
assert.equal(A.filterWords("a b c d e f g h".replace(/\w/g, (c) => "girl " + c)).length, 1, "each word once");
|
||||
assert.equal(A.filterWords(["young", "youth", "teen", "teenager", "tween", "kid", "child", "baby", "petite", "pupil"].join(" ")).length, 8, "at most eight");
|
||||
assert.deepEqual(A.filterWords(""), []);
|
||||
assert.deepEqual(A.filterWords(null), []);
|
||||
});
|
||||
|
||||
// ---------- on their own ----------
|
||||
test("on their own: a move is considered when asked to show or say something, when the reply shows something, or now and then", () => {
|
||||
const u = (content, extra = {}) => ({ role: "user", content, ...extra }), a = (content, extra = {}) => ({ role: "assistant", content, ...extra });
|
||||
|
||||
@@ -724,12 +724,23 @@ async function main() {
|
||||
// the photo model turns the photo down: the panel says so, with the next photo model a click away
|
||||
refusing.add("grok-imagine-edit");
|
||||
const editsWren = seen.edits.length, refusedWren = seen.refused.length;
|
||||
assert.equal(await page.locator("#ask-saw").count(), 0, "nothing to see before a refusal");
|
||||
await page.click("#ask-go");
|
||||
await has(page, "#ask .status", /^Venice turned that down as against its content rules\. Change the shot, or try another model\.$/);
|
||||
assert.deepEqual(seen.refused.slice(refusedWren), ["grok-imagine-edit"]);
|
||||
assert.equal(seen.edits.length, editsWren, "nothing made");
|
||||
await has(page, "#ask-alt", /^TRY FIRERED · \$0\.04$/);
|
||||
await has(page, "#ask-go", /^TRY AGAIN · \$0\.03$/);
|
||||
// what venice saw: the call, its answer as it came, the words a filter may weigh, and the prompt, a click away
|
||||
await has(page, "#ask details.saw summary", /^what venice saw$/);
|
||||
assert.equal(await page.locator("#ask details.saw").evaluate((d) => d.open), false, "folded until asked");
|
||||
const sawWren = await textOf(page, "#ask-saw");
|
||||
assert.match(sawWren, /^Grok Imagine \(grok-imagine-edit\) · image\/edit · 1 picture · safe mode on\n/);
|
||||
assert.match(sawWren, /\nvenice: 422 — Your prompt violates the content policy of Venice\.ai or the model provider\n/);
|
||||
assert.match(sawWren, /\nwords a filter may weigh \(ages, youth\): none havre knows of\. a real person's name, or a public figure's, is turned down too\.\nprompt:\n/);
|
||||
assert.match(sawWren, /\nprompt:\nThe reference picture shows Wren\. Safe for work: everyone in it is an adult, fully clothed\. Keep their face, so it's clearly them\. How they look: Thirties, wiry.*New photo: .*moving boxes.* A natural photo with no text in it\.$/s);
|
||||
await page.locator("#ask details.saw summary").click();
|
||||
await has(page, "#ask details.saw .acts button", /^COPY$/);
|
||||
await shot(page, "desktop-ask-refused.png");
|
||||
await page.click("#ask-alt");
|
||||
await has(page, "#toast", /^Photo model: FireRed, from now on\.$/);
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
export const BASE = "https://api.venice.ai/api/v1";
|
||||
|
||||
export class VeniceError extends Error {
|
||||
/** refused: Venice turned it down as against its content rules (its own, or the ones of the model's maker) */
|
||||
/**
|
||||
* refused: Venice turned it down as against its content rules (its own, or the ones of the model's maker). One that
|
||||
* comes from Venice's answer also carries the answer (said, code, body), and one from a picture or clip call carries
|
||||
* what was sent (sent: endpoint, model, pictures, safe_mode, prompt), for the page's "what venice saw".
|
||||
*/
|
||||
constructor(message, status, refused = false) { super(message); this.status = status; this.refused = !!refused; }
|
||||
}
|
||||
|
||||
@@ -19,7 +23,17 @@ function failure(status, body) {
|
||||
const said = String((e && typeof e === "object" ? e.message : e) || (body && body.message) || "").slice(0, 240);
|
||||
const code = String((e && typeof e === "object" && e.code) || (body && body.code) || "");
|
||||
const refused = status === 422 && (/CONTENT_POLICY/i.test(code) || /content polic|violat|moderat|blocked|safety/i.test(said) || !said);
|
||||
return new VeniceError(explain(status, said, refused), status, refused);
|
||||
const err = new VeniceError(explain(status, said, refused), status, refused);
|
||||
// Venice's answer as it came, for the "what venice saw" block: its words, its code, and the body when it says nothing
|
||||
err.said = said;
|
||||
err.code = code;
|
||||
err.body = body == null ? "" : (() => { try { return JSON.stringify(body).slice(0, 600); } catch { return ""; } })();
|
||||
return err;
|
||||
}
|
||||
/** what a picture or a clip call sent, kept on the error it failed with (sent), so the page can show it */
|
||||
function sent(e, what) {
|
||||
if (e && e.name !== "AbortError") e.sent = what;
|
||||
return e;
|
||||
}
|
||||
function explain(status, said, refused) {
|
||||
// Venice's own words for it ("violates the content policy of Venice.ai or the model provider") say no more than this
|
||||
@@ -103,10 +117,13 @@ export class Venice {
|
||||
async editImage({ model, prompt, images, safe = true, signal } = {}) {
|
||||
const pics = (images || []).filter(Boolean);
|
||||
if (!pics.length) throw new VeniceError("A photo needs at least one picture of the character.", 0);
|
||||
const safe_mode = safe !== false;
|
||||
const res = pics.length === 1
|
||||
? await this.raw("/image/edit", { body: { model, prompt, image: imageInput(pics[0]), safe_mode, output_format: "webp" }, signal })
|
||||
: await this.raw("/image/multi-edit", { body: { modelId: model, prompt, images: pics.map(imageInput), safe_mode, output_format: "webp" }, signal });
|
||||
const safe_mode = safe !== false, endpoint = pics.length === 1 ? "image/edit" : "image/multi-edit";
|
||||
let res;
|
||||
try {
|
||||
res = pics.length === 1
|
||||
? await this.raw("/image/edit", { body: { model, prompt, image: imageInput(pics[0]), safe_mode, output_format: "webp" }, signal })
|
||||
: await this.raw("/image/multi-edit", { body: { modelId: model, prompt, images: pics.map(imageInput), safe_mode, output_format: "webp" }, signal });
|
||||
} catch (e) { throw sent(e, { endpoint, model, pictures: pics.length, safe_mode, prompt }); }
|
||||
return { blob: await res.blob(), blurred: res.headers && res.headers.get ? res.headers.get("x-venice-is-blurred") === "true" : false };
|
||||
}
|
||||
|
||||
@@ -117,7 +134,9 @@ export class Venice {
|
||||
async generateImage({ model, prompt, aspect = "", safe = true, signal } = {}) {
|
||||
const body = { model, prompt, safe_mode: safe !== false, format: "webp", return_binary: true };
|
||||
if (aspect) body.aspect_ratio = aspect;
|
||||
const res = await this.raw("/image/generate", { body, signal });
|
||||
let res;
|
||||
try { res = await this.raw("/image/generate", { body, signal }); }
|
||||
catch (e) { throw sent(e, { endpoint: "image/generate", model, pictures: 0, safe_mode: body.safe_mode, prompt }); }
|
||||
return { blob: await res.blob(), blurred: res.headers && res.headers.get ? res.headers.get("x-venice-is-blurred") === "true" : false };
|
||||
}
|
||||
|
||||
@@ -163,7 +182,9 @@ export class Venice {
|
||||
const pics = images.filter(Boolean);
|
||||
if (/reference-to-video/.test(model)) { if (pics.length) body.reference_image_urls = pics.slice(0, 4); }
|
||||
else if (pics.length) body.image_url = pics[0];
|
||||
const d = await this.call("/video/queue", { method: "POST", body, signal });
|
||||
let d;
|
||||
try { d = await this.call("/video/queue", { method: "POST", body, signal }); }
|
||||
catch (e) { throw sent(e, { endpoint: "video/queue", model, pictures: Math.min(pics.length, /reference-to-video/.test(model) ? 4 : 1), prompt }); }
|
||||
if (!d || !d.queue_id) throw new VeniceError("Venice didn't start the clip.", 0);
|
||||
return { queueId: d.queue_id, downloadUrl: typeof d.download_url === "string" ? d.download_url : "" };
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user