From dd4381c25c4207c779bdd95320fe9b11d7c51ad1 Mon Sep 17 00:00:00 2001 From: Debanjum Date: Sat, 29 Nov 2025 15:09:21 -0800 Subject: [PATCH] Do not try render invalid image paths in message on web app Avoids rendering flicker from attempt to render invalid image paths referenced in message by khoj on web app. The rendering flicker made it very annoying to interact with conversations containing such messages on the web app. The current change does lightweight validation of image url before attempting to render it. If invalid image url detected, the image is replaced with just its alt text. --- .../components/chatMessage/chatMessage.tsx | 2 + .../chatMessage/imageValidationPlugin.ts | 70 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/interface/web/app/components/chatMessage/imageValidationPlugin.ts 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); + }; +}