diff --git a/src/interface/web/app/components/chatMessage/chatMessage.tsx b/src/interface/web/app/components/chatMessage/chatMessage.tsx index 4251ca5a..ebd32b04 100644 --- a/src/interface/web/app/components/chatMessage/chatMessage.tsx +++ b/src/interface/web/app/components/chatMessage/chatMessage.tsx @@ -16,6 +16,7 @@ import { } from "@/app/components/referencePanel/referencePanel"; import { renderCodeGenImageInline } from "@/app/common/chatFunctions"; import { fileLinksPlugin } from "@/app/components/chatMessage/fileLinksPlugin"; +import { imageValidationPlugin } from "@/app/components/chatMessage/imageValidationPlugin"; import FileContentSnippet from "@/app/components/chatMessage/FileContentSnippet"; import { useFileContent } from "@/app/components/chatMessage/useFileContent"; @@ -75,6 +76,7 @@ md.use(mditHljs, { }); md.use(fileLinksPlugin); +md.use(imageValidationPlugin); export interface Context { compiled: string; diff --git a/src/interface/web/app/components/chatMessage/imageValidationPlugin.ts b/src/interface/web/app/components/chatMessage/imageValidationPlugin.ts new file mode 100644 index 00000000..3ec808a7 --- /dev/null +++ b/src/interface/web/app/components/chatMessage/imageValidationPlugin.ts @@ -0,0 +1,70 @@ +import MarkdownIt from "markdown-it"; + +/** + * Checks if a URL is a valid, loadable image URL. + * Returns true for URLs that browsers can actually fetch: + * - data: URLs (base64 encoded) + * - blob: URLs (object URLs) + * - http:// and https:// URLs + */ +function isValidImageUrl(url: string): boolean { + if (!url || typeof url !== "string") { + return false; + } + + const trimmedUrl = url.trim(); + + // Allow data URLs (base64 encoded images) + if (trimmedUrl.startsWith("data:")) { + return true; + } + + // Allow blob URLs + if (trimmedUrl.startsWith("blob:")) { + return true; + } + + // Allow HTTP/HTTPS URLs + if (trimmedUrl.startsWith("http://") || trimmedUrl.startsWith("https://")) { + return true; + } + + // Reject everything else (file://, relative paths, absolute paths, etc.) + return false; +} + +/** + * Image validation plugin for markdown-it + * Filters out images with invalid/non-loadable URLs at render time. + * This prevents broken images from ever being added to the DOM. + */ +export function imageValidationPlugin(md: MarkdownIt) { + // Store the original image renderer + const defaultImageRenderer = + md.renderer.rules.image || + function (tokens, idx, options, env, self) { + return self.renderToken(tokens, idx, options); + }; + + // Override the image renderer + md.renderer.rules.image = function (tokens, idx, options, env, self) { + const token = tokens[idx]; + const srcIndex = token.attrIndex("src"); + + if (srcIndex >= 0 && token.attrs) { + const src = token.attrs[srcIndex][1]; + + // If the URL is not valid, don't render the image at all + if (!isValidImageUrl(src)) { + // Return alt text as fallback, or empty string + const altText = token.content || ""; + if (altText) { + return `[Image: ${md.utils.escapeHtml(altText)}]`; + } + return ""; + } + } + + return defaultImageRenderer(tokens, idx, options, env, self); + }; +}