CoolFace
Datasetpublic

gradio/frontend

sourceHugging Faceupdated 12h agoView on Hugging Face
1likes413kdownloads
MarkdownCode.svelte242 linesDownload Raw Back to markdown-code
1<script lang="ts">2	import { afterUpdate, tick, onMount } from "svelte";3	import { create_marked } from "./utils";4	import { sanitize } from "@gradio/sanitize";5	import "./prism.css";6	import { standardHtmlAndSvgTags } from "./html-tags";7	import type { ThemeMode } from "@gradio/core";8 9	export let chatbot = true;10	export let message: string;11	export let sanitize_html = true;12	export let latex_delimiters: {13		left: string;14		right: string;15		display: boolean;16	}[] = [];17	export let render_markdown = true;18	export let line_breaks = true;19	export let header_links = false;20	export let allow_tags: string[] | boolean = false;21	export let theme_mode: ThemeMode = "system";22	let el: HTMLSpanElement;23	let html: string;24	let katex_loaded = false;25 26	const marked = create_marked({27		header_links,28		line_breaks,29		latex_delimiters: latex_delimiters || []30	});31 32	function has_math_syntax(text: string): boolean {33		if (!latex_delimiters || latex_delimiters.length === 0) {34			return false;35		}36 37		return latex_delimiters.some(38			(delimiter) =>39				text.includes(delimiter.left) && text.includes(delimiter.right)40		);41	}42 43	function escapeRegExp(string: string): string {44		return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");45	}46 47	function escapeTags(48		content: string,49		tagsToEscape: string[] | boolean50	): string {51		if (tagsToEscape === true) {52			// https://www.w3schools.com/tags/53			const tagRegex = /<\/?([a-zA-Z][a-zA-Z0-9-]*)([\s>])/g;54			return content.replace(tagRegex, (match, tagName, endChar) => {55				if (!standardHtmlAndSvgTags.includes(tagName.toLowerCase())) {56					return match.replace(/</g, "&lt;").replace(/>/g, "&gt;");57				}58				return match;59			});60		}61 62		if (Array.isArray(tagsToEscape)) {63			const tagPattern = tagsToEscape.map((tag) => ({64				open: new RegExp(`<(${tag})(\\s+[^>]*)?>`, "gi"),65				close: new RegExp(`</(${tag})>`, "gi")66			}));67 68			let result = content;69 70			tagPattern.forEach((pattern) => {71				result = result.replace(pattern.open, (match) =>72					match.replace(/</g, "&lt;").replace(/>/g, "&gt;")73				);74				result = result.replace(pattern.close, (match) =>75					match.replace(/</g, "&lt;").replace(/>/g, "&gt;")76				);77			});78			return result;79		}80		return content;81	}82 83	function process_message(value: string): string {84		let parsedValue = value;85		if (render_markdown) {86			const latexBlocks: string[] = [];87			latex_delimiters.forEach((delimiter, index) => {88				const leftDelimiter = escapeRegExp(delimiter.left);89				const rightDelimiter = escapeRegExp(delimiter.right);90				const regex = new RegExp(91					`${leftDelimiter}([\\s\\S]+?)${rightDelimiter}`,92					"g"93				);94				parsedValue = parsedValue.replace(regex, (match, p1) => {95					latexBlocks.push(match);96					return `%%%LATEX_BLOCK_${latexBlocks.length - 1}%%%`;97				});98			});99 100			parsedValue = marked.parse(parsedValue) as string;101 102			parsedValue = parsedValue.replace(103				/%%%LATEX_BLOCK_(\d+)%%%/g,104				(match, p1) => latexBlocks[parseInt(p1, 10)]105			);106		}107 108		if (allow_tags) {109			parsedValue = escapeTags(parsedValue, allow_tags);110		}111 112		if (sanitize_html && sanitize) {113			parsedValue = sanitize(parsedValue);114		}115		return parsedValue;116	}117 118	$: if (message && message.trim()) {119		html = process_message(message);120	} else {121		html = "";122	}123 124	async function render_html(value: string): Promise<void> {125		if (latex_delimiters.length > 0 && value && has_math_syntax(value)) {126			if (!katex_loaded) {127				await Promise.all([128					import("katex/dist/katex.min.css"),129					import("katex/contrib/auto-render")130				]).then(([, { default: render_math_in_element }]) => {131					katex_loaded = true;132					render_math_in_element(el, {133						delimiters: latex_delimiters,134						throwOnError: false135					});136				});137			} else {138				const { default: render_math_in_element } = await import(139					"katex/contrib/auto-render"140				);141				render_math_in_element(el, {142					delimiters: latex_delimiters,143					throwOnError: false144				});145			}146		}147 148		if (el) {149			const mermaidDivs = el.querySelectorAll(".mermaid");150			if (mermaidDivs.length > 0) {151				await tick();152				const { default: mermaid } = await import("mermaid");153 154				mermaid.initialize({155					startOnLoad: false,156					theme: theme_mode === "dark" ? "dark" : "default",157					securityLevel: "antiscript"158				});159				await mermaid.run({160					nodes: Array.from(mermaidDivs).map((node) => node as HTMLElement)161				});162			}163		}164	}165 166	afterUpdate(async () => {167		if (el && document.body.contains(el)) {168			await render_html(message);169		} else {170			console.error("Element is not in the DOM");171		}172	});173</script>174 175<span class:chatbot bind:this={el} class="md" class:prose={render_markdown}>176	{@html html}177</span>178 179<style>180	span :global(div[class*="code_wrap"]) {181		position: relative;182	}183 184	/* KaTeX */185	span :global(span.katex) {186		font-size: var(--text-lg);187		direction: ltr;188	}189 190	span :global(div[class*="code_wrap"] > button) {191		z-index: 1;192		cursor: pointer;193		border-bottom-left-radius: var(--radius-sm);194		padding: var(--spacing-md);195		width: 25px;196		height: 25px;197		position: absolute;198		right: 0;199	}200 201	span :global(.check) {202		opacity: 0;203		z-index: var(--layer-top);204		transition: opacity 0.2s;205		background: var(--code-background-fill);206		color: var(--body-text-color);207		position: absolute;208		top: var(--size-1-5);209		left: var(--size-1-5);210	}211 212	span :global(p:not(:first-child)) {213		margin-top: var(--spacing-xxl);214	}215 216	span :global(.md-header-anchor) {217		/* position: absolute; */218		margin-left: -25px;219		padding-right: 8px;220		line-height: 1;221		color: var(--body-text-color-subdued);222		opacity: 0;223	}224 225	span :global(h1:hover .md-header-anchor),226	span :global(h2:hover .md-header-anchor),227	span :global(h3:hover .md-header-anchor),228	span :global(h4:hover .md-header-anchor),229	span :global(h5:hover .md-header-anchor),230	span :global(h6:hover .md-header-anchor) {231		opacity: 1;232	}233 234	span.md :global(.md-header-anchor > svg) {235		color: var(--body-text-color-subdued);236	}237 238	span :global(table) {239		word-break: break-word;240	}241</style>242 
gradio/frontend · CoolFace