ai-chatbot/lib/video-canvas/aliyun-video-analysis.ts

93 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

type AliyunVideoModel = "qwen3.8-max" | "qwen3.8-flash" | "qwen3.8-omni-flash";
export function isAliyunVideoModel(modelId: string, providerType: string) {
return providerType === "alibaba" &&
["qwen3.8-max", "qwen3.8-flash", "qwen3.8-omni-flash"].includes(modelId);
}
export function supportsVideoSound(modelId: string) {
return modelId === "qwen3.8-omni-flash";
}
function officialEndpoint(baseUrl: string) {
const url = new URL(baseUrl);
if (url.protocol !== "https:" ||
!(url.hostname === "dashscope.aliyuncs.com" ||
/^[-a-z0-9]+\.[-a-z0-9]+\.maas\.aliyuncs\.com$/.test(url.hostname))) {
throw new Error("视频解析仅允许阿里云百炼官方地址");
}
return `${url.href.replace(/\/$/, "")}/chat/completions`;
}
function sseText(body: string) {
let result = "";
for (const line of body.split(/\r?\n/)) {
if (!line.startsWith("data: ") || line === "data: [DONE]") continue;
try {
const event = JSON.parse(line.slice(6));
result += event.choices?.[0]?.delta?.content || "";
} catch {
// Ignore keep-alive and incomplete events; a missing result is rejected below.
}
}
return result;
}
export async function analyzeAliyunVideo({
apiKey,
baseUrl,
modelId,
prompt,
videoUrl,
signal,
}: {
apiKey: string;
baseUrl: string;
modelId: AliyunVideoModel;
prompt: string;
videoUrl: string;
signal?: AbortSignal;
}) {
const video = new URL(videoUrl);
if (video.protocol !== "https:") {
throw new Error("阿里云视频解析需要可公网访问的 HTTPS 视频地址");
}
const omni = supportsVideoSound(modelId);
const response = await fetch(officialEndpoint(baseUrl), {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: modelId,
messages: [{
role: "user",
content: [
{ type: "video_url", video_url: { url: video.href }, fps: 1 },
{ type: "text", text: prompt },
],
}],
stream: omni,
...(omni ? { modalities: ["text"], reasoning_effort: "none" } : {
temperature: 0.2,
max_tokens: 6000,
}),
}),
signal,
});
if (!response.ok) {
let code = "";
try {
const body = await response.json();
code = String(body.error?.code || body.code || "").slice(0, 80);
} catch {}
throw new Error(`阿里云视频解析失败(HTTP ${response.status}${code ? `,${code}` : ""})`);
}
const text = omni
? sseText(await response.text())
: String((await response.json()).choices?.[0]?.message?.content || "");
if (!text.trim()) throw new Error("阿里云视频解析返回空结果");
return text;
}