2026-05-27 10:02:11 +08:00

146 lines
3.7 KiB
TypeScript

import { inferOriginalImageUrl } from "@/lib/image-url";
import type { ImageNodeData } from "@/types/canvas";
export function inferModelFormat(modelUrl?: string | null) {
const value = String(modelUrl || "").toLowerCase();
if (!value) return "glb";
if (value.includes(".gltf")) return "gltf";
if (value.includes(".glb")) return "glb";
if (value.includes(".usdz")) return "usdz";
if (value.includes(".fbx")) return "fbx";
if (value.includes(".obj")) return "obj";
if (value.includes(".stl")) return "stl";
if (value.includes(".3mf")) return "3mf";
return "glb";
}
export function inferTripoImageType(imageUrl?: string | null) {
const value = String(imageUrl || "").toLowerCase();
if (value.includes(".png")) return "png";
if (value.includes(".webp")) return "webp";
return "jpg";
}
export const normalizeMediaUrl = (url: string): string => {
const raw = String(url || "").trim();
if (!raw) return "";
try {
const parsed = new URL(raw);
return `${parsed.origin}${parsed.pathname}`
.replace(/\/+$/, "")
.toLowerCase();
} catch {
return raw.split("#")[0].split("?")[0].replace(/\/+$/, "").toLowerCase();
}
};
export const normalizeImageIdentityUrl = (url: string): string => {
const raw = String(url || "").trim();
if (!raw) return "";
const canonical = inferOriginalImageUrl(raw);
return normalizeMediaUrl(canonical || raw);
};
export const resolveOriginalImageUrl = (url?: string | null): string => {
const raw = String(url || "").trim();
return String(inferOriginalImageUrl(raw) || raw).trim();
};
export const normalizeImageTitle = (raw?: string) => {
const title = (raw || "").trim();
return title || "";
};
export const getImageNodeTitle = (data: ImageNodeData, nodeId: string) => {
return (
normalizeImageTitle(data.title) ||
normalizeImageTitle(data.prompt) ||
`图片-${nodeId.slice(0, 6)}`
);
};
export const sanitizeFileName = (name: string) => {
const safe = name
.replace(/[<>:"/\\|?*\x00-\x1F]/g, "-")
.replace(/\s+/g, " ")
.trim()
.slice(0, 120);
return safe || "image";
};
export const triggerBrowserDownload = (
url: string,
filename?: string,
options?: { openInNewTab?: boolean }
) => {
const link = document.createElement("a");
link.href = url;
if (filename) {
link.download = filename;
}
if (options?.openInNewTab) {
link.target = "_blank";
}
link.rel = "noopener noreferrer";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
const GENERIC_CANVAS_PROMPTS = new Set([
"对话生成",
"图片生成",
"AI生成",
"智能生成",
"自动生成",
]);
export const normalizePromptText = (value: unknown): string => {
return typeof value === "string" ? value.trim() : "";
};
export const resolveRegeneratePromptSeed = (data: ImageNodeData): string => {
const metadataPrompt = normalizePromptText(data.metadata?.generationPrompt);
if (metadataPrompt) return metadataPrompt;
const nodePrompt = normalizePromptText(data.prompt);
if (nodePrompt && !GENERIC_CANVAS_PROMPTS.has(nodePrompt)) {
return nodePrompt;
}
return nodePrompt;
};
const COMMON_ASPECT_RATIOS = [
"1:1",
"2:3",
"3:2",
"3:4",
"4:3",
"4:5",
"5:4",
"9:16",
"16:9",
"21:9",
] as const;
export function guessNearestAspectRatio(width: number, height: number): string {
if (!width || !height) return "auto";
const target = width / height;
let best = "auto";
let minDiff = Number.POSITIVE_INFINITY;
for (const ratio of COMMON_ASPECT_RATIOS) {
const [w, h] = ratio.split(":").map(Number);
const value = w / h;
const diff = Math.abs(target - value);
if (diff < minDiff) {
minDiff = diff;
best = ratio;
}
}
return best;
}