/** * @fileoverview Link component for rendering tags in Markdown. * Extracted from the main markdown renderer for modularity. * Handles special rendering for "abbr:" type links for interactive chat actions. */ import * as React from 'react' import { useChatContext } from '@/app/components/base/chat/chat/context' import { isValidUrl } from './utils' const filePreviewPathPattern = /\/files\/[^/?#]+\/file-preview(?:[?#]|$)/ const asAttachmentParamPattern = /[?&]as_attachment=/ const withFilePreviewAttachment = (href: string) => { if (!filePreviewPathPattern.test(href) || asAttachmentParamPattern.test(href)) return href const url = new URL(href, 'http://dify.local') url.searchParams.set('as_attachment', 'true') return href.startsWith('//') ? url.href.replace(url.protocol, '') : url.href } const Link = ({ node, children, ...props }: any) => { const { onSend } = useChatContext() const commonClassName = 'cursor-pointer underline decoration-primary-700! decoration-dashed' if (node.properties?.href && node.properties.href?.toString().startsWith('abbr')) { const hidden_text = decodeURIComponent(node.properties.href.toString().split('abbr:')[1]) return ( onSend?.(hidden_text)} title={node.children[0]?.value || ''} > {node.children[0]?.value || ''} ) } else { const rawHref = props.href || node.properties?.href const href = rawHref?.toString() if (href && /^#[\w-]+$/.test(href)) { const handleClick = (e: React.MouseEvent) => { e.preventDefault() // scroll to target element if exists within the answer container const answerContainer = e.currentTarget.closest('.chat-answer-container') if (answerContainer) { const targetId = CSS.escape(href.toString().substring(1)) const targetElement = answerContainer.querySelector(`[id="${targetId}"]`) if (targetElement) targetElement.scrollIntoView({ behavior: 'smooth' }) } } return ( {children || 'ScrollView'} ) } if (!href || !isValidUrl(href)) return {children} return ( {children || 'Download'} ) } } export default Link