ai-chatbot/lib/realtime/image-input.ts
Your Name eb6a0bf7dd
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
fix: upload realtime replicate inputs before inference
2026-03-27 21:48:49 +08:00

93 lines
2.4 KiB
TypeScript

import "server-only";
import { uploadFile } from "@/lib/storage/service";
const DATA_IMAGE_URL_PATTERN = /^data:(image\/[a-z0-9.+-]+);base64,([a-z0-9+/=\s]+)$/i;
const MIME_TYPE_EXTENSION_MAP: Record<string, string> = {
"image/jpeg": "jpg",
"image/jpg": "jpg",
"image/png": "png",
"image/webp": "webp",
"image/gif": "gif",
"image/bmp": "bmp",
"image/tiff": "tiff",
"image/svg+xml": "svg",
};
function normalizeImageMimeType(mimeType: string) {
return String(mimeType || "").trim().toLowerCase();
}
function resolveImageExtension(mimeType: string) {
const normalized = normalizeImageMimeType(mimeType);
return MIME_TYPE_EXTENSION_MAP[normalized] || normalized.split("/")[1] || "png";
}
function toArrayBuffer(buffer: Buffer) {
return buffer.buffer.slice(
buffer.byteOffset,
buffer.byteOffset + buffer.byteLength
) as ArrayBuffer;
}
export function parseDataImageUrl(raw: string) {
const trimmed = String(raw || "").trim();
const match = trimmed.match(DATA_IMAGE_URL_PATTERN);
if (!match) return null;
const mimeType = normalizeImageMimeType(match[1]);
const base64Payload = match[2].replace(/\s+/g, "");
return {
mimeType,
base64Payload,
extension: resolveImageExtension(mimeType),
};
}
export async function uploadDataImageUrl(params: {
imageUrl: string;
prefix: string;
}) {
const parsed = parseDataImageUrl(params.imageUrl);
if (!parsed) {
throw new Error("图片必须是有效的 data:image;base64 格式");
}
const buffer = Buffer.from(parsed.base64Payload, "base64");
const filename = `${params.prefix}-${Date.now()}-${Math.random()
.toString(36)
.slice(2, 8)}.${parsed.extension}`;
const uploaded = await uploadFile(filename, toArrayBuffer(buffer));
return uploaded.url;
}
export async function ensurePublicImageUrl(params: {
imageUrl: string;
prefix: string;
requestOrigin?: string;
}) {
const trimmed = String(params.imageUrl || "").trim();
if (!trimmed) {
throw new Error("缺少输入图片");
}
if (/^https?:\/\//i.test(trimmed)) {
return trimmed;
}
if (params.requestOrigin && trimmed.startsWith("/")) {
return new URL(trimmed, params.requestOrigin).toString();
}
if (trimmed.startsWith("data:")) {
return uploadDataImageUrl({
imageUrl: trimmed,
prefix: params.prefix,
});
}
throw new Error("图片必须是公网 URL、站内相对路径或 data:image;base64");
}