CoolFace
Datasetpublic

gradio/frontend

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