146 lines
3.7 KiB
TypeScript
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;
|
|
}
|