const MARKDOWN_IMAGE_REGEX = /!\[([^\]]*)\]\((https?:\/\/[^\s)]+)\)/gim; const PLAIN_IMAGE_URL_REGEX = /https?:\/\/[^\s"'<>]+\.(?:jpg|jpeg|png|gif|webp|bmp|svg)(?:\?[^\s"'<>]*)?/gi; function isHttpUrl(value: string): boolean { return /^https?:\/\//i.test(value); } export function isLikelyThumbnailUrl(url: string): boolean { const lowerUrl = url.toLowerCase(); if ( lowerUrl.includes("/thumb/") || lowerUrl.includes("thumbnail") || lowerUrl.includes("gemini-thumb-") || lowerUrl.includes("openai-thumb-") || lowerUrl.includes("volcano-thumb-") || lowerUrl.includes("alibaba-image-thumb-") || lowerUrl.includes("ruxa-image-thumb-") || lowerUrl.includes("fal-image-thumb-") ) { return true; } return /(?:^|[-_/])thumb(?:[-_.\\/]|$)/i.test(url); } export function inferOriginalImageUrl(url: string): string { if (!isHttpUrl(url)) return url; if (url.includes("gemini-thumb-")) { return url.replace("gemini-thumb-", "gemini-orig-"); } if (url.includes("openai-thumb-")) { return url.replace("openai-thumb-", "openai-orig-"); } if (url.includes("volcano-thumb-")) { return url.replace("volcano-thumb-", "volcano-orig-"); } if (url.includes("alibaba-image-thumb-")) { return url.replace("alibaba-image-thumb-", "alibaba-image-orig-"); } if (url.includes("ruxa-image-thumb-")) { return url.replace("ruxa-image-thumb-", "ruxa-image-orig-"); } if (url.includes("fal-image-thumb-")) { return url.replace("fal-image-thumb-", "fal-image-orig-"); } if (url.includes("/thumb/")) { return url.replace("/thumb/", "/"); } if (url.includes("-thumb.")) { return url.replace("-thumb.", "."); } return url; } export function extractOriginalImageUrlFromAlt(altText: string, fallbackUrl: string): string { let originalUrl = fallbackUrl; if (altText.includes("thumb|||")) { const content = altText.substring(altText.indexOf("thumb|||") + 8); const parts = content.split("|||"); if (parts[0] && isHttpUrl(parts[0])) { originalUrl = parts[0]; } } else if (altText.includes("thumb$$")) { const content = altText.substring(altText.indexOf("thumb$$") + 7); const parts = content.split("$$"); if (parts[0] && isHttpUrl(parts[0])) { originalUrl = parts[0]; } } else if (altText.includes("thumb|")) { const content = altText.substring(altText.indexOf("thumb|") + 6); const parts = content.split("|"); if (parts[0] && isHttpUrl(parts[0])) { originalUrl = parts[0]; } } if (originalUrl === fallbackUrl || !isHttpUrl(originalUrl)) { originalUrl = inferOriginalImageUrl(fallbackUrl); } return originalUrl; } export function extractImageUrlsFromText(text: string): string[] { if (!text) return []; const urls: string[] = []; const added = new Set(); const addUrl = (url: string) => { if (!url || !isHttpUrl(url) || added.has(url)) return; added.add(url); urls.push(url); }; let hasMarkdownImages = false; let markdownMatch: RegExpExecArray | null; MARKDOWN_IMAGE_REGEX.lastIndex = 0; while ((markdownMatch = MARKDOWN_IMAGE_REGEX.exec(text)) !== null) { hasMarkdownImages = true; const altText = markdownMatch[1] || ""; const thumbnailUrl = markdownMatch[2] || ""; const originalUrl = extractOriginalImageUrlFromAlt(altText, thumbnailUrl); addUrl(originalUrl || thumbnailUrl); } if (hasMarkdownImages) return urls; const rawUrls = text.match(PLAIN_IMAGE_URL_REGEX) || []; if (!rawUrls.length) return urls; const nonThumbnailUrls = rawUrls.filter((url) => !isLikelyThumbnailUrl(url)); const candidates = nonThumbnailUrls.length ? nonThumbnailUrls : rawUrls.map((url) => inferOriginalImageUrl(url)); for (const url of candidates) { addUrl(url); } return urls; }