From 24e906998a37b27ff77f7692cda7bb1ee9c37c48 Mon Sep 17 00:00:00 2001 From: ekko <152005280+EKKOLearnAI@users.noreply.github.com> Date: Sat, 16 May 2026 10:43:51 +0800 Subject: [PATCH] support windows markdown media paths (#780) --- .../hermes/chat/MarkdownRenderer.vue | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/client/src/components/hermes/chat/MarkdownRenderer.vue b/packages/client/src/components/hermes/chat/MarkdownRenderer.vue index 213a293..f30ef7f 100644 --- a/packages/client/src/components/hermes/chat/MarkdownRenderer.vue +++ b/packages/client/src/components/hermes/chat/MarkdownRenderer.vue @@ -57,29 +57,30 @@ const previewUrl = ref(null) let renderGeneration = 0 let unmounted = false +function isLocalFilePath(path: string): boolean { + return path.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(path) +} + +function normalizeLocalFilePath(path: string): string { + return /^[a-zA-Z]:\\/.test(path) ? path.replace(/\\/g, '/') : path +} + const renderedHtml = computed(() => { let html = md.render(repairNestedMarkdownFences(props.content)) // Replace image src paths with download URLs - // Replace both src="/path" and src='/path' formats - html = html.replace(/src="\/([^"]+)"/g, (_match, path) => { - const originalPath = '/' + path - const downloadUrl = getDownloadUrl(originalPath) - return `src="${downloadUrl}"` - }) - - html = html.replace(/src='\/([^']+)'/g, (_match, path) => { - const originalPath = '/' + path - const downloadUrl = getDownloadUrl(originalPath) - return `src='${downloadUrl}'` + html = html.replace(/\bsrc=(["'])([^"']+)\1/g, (match, quote, path) => { + if (!isLocalFilePath(path)) return match + const downloadUrl = getDownloadUrl(normalizeLocalFilePath(path)) + return `src=${quote}${downloadUrl}${quote}` }) // Replace local file links with file card UI or video player - // Match filename or filename - html = html.replace(/([^<]+)<\/a>/g, (match, path, filename) => { - // Only replace local file paths (starting with /) - if (!path.startsWith('/')) return match + // Match filename or filename + html = html.replace(/([^<]+)<\/a>/g, (match, rawPath, filename) => { + if (!isLocalFilePath(rawPath)) return match + const path = normalizeLocalFilePath(rawPath) const fileName = filename.trim() const ext = path.split('.').pop()?.toLowerCase() @@ -323,13 +324,13 @@ async function handleMarkdownClick(event: MouseEvent): Promise { } // File path links: intercept and download - if (href.startsWith('/')) { + if (isLocalFilePath(href)) { event.preventDefault() event.stopPropagation() const linkText = link.textContent || '' const fileName = linkText.startsWith('File: ') ? linkText.slice(6).trim() : linkText.trim() message.info(t('download.downloading')) - downloadFile(href, fileName || undefined).catch((err: Error) => { + downloadFile(normalizeLocalFilePath(href), fileName || undefined).catch((err: Error) => { message.error(err.message || t('download.downloadFailed')) }) }