import { marked, Renderer } from "marked"; import { markedHighlight } from "marked-highlight"; import Prism from "prismjs"; import "prismjs/components/prism-python"; import "prismjs/components/prism-latex"; import type { FileData } from "@gradio/upload"; import { uploadToHuggingFace } from "@gradio/utils"; import type { ActionReturn } from "svelte/action"; const COPY_ICON_CODE = ``; const CHECK_ICON_CODE = ``; const COPY_BUTTON_CODE = ``; const escape_test = /[&<>"']/; const escape_replace = new RegExp(escape_test.source, "g"); const escape_test_no_encode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/; const escape_replace_no_encode = new RegExp(escape_test_no_encode.source, "g"); const escape_replacements: Record = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }; const get_escape_replacement = (ch: string): string => escape_replacements[ch] || ""; function escape(html: string, encode?: boolean): string { if (encode) { if (escape_test.test(html)) { return html.replace(escape_replace, get_escape_replacement); } } else { if (escape_test_no_encode.test(html)) { return html.replace(escape_replace_no_encode, get_escape_replacement); } } return html; } const renderer: Partial< Omit, "constructor" | "options"> > = { code( this: Renderer, code: string, infostring: string | undefined, escaped: boolean ) { const lang = (infostring ?? "").match(/\S*/)?.[0] ?? ""; if (this.options.highlight) { const out = this.options.highlight(code, lang); if (out != null && out !== code) { escaped = true; code = out; } } code = code.replace(/\n$/, "") + "\n"; if (!lang) { return ( '
' + COPY_BUTTON_CODE + "
" +
				(escaped ? code : escape(code, true)) +
				"
\n" ); } return ( '
' + COPY_BUTTON_CODE + '
' +
			(escaped ? code : escape(code, true)) +
			"
\n" ); } }; marked.use( { gfm: true, breaks: true, pedantic: false, headerIds: false, mangle: false }, markedHighlight({ highlight: (code: string, lang: string) => { if (Prism.languages[lang]) { return Prism.highlight(code, Prism.languages[lang], lang); } return code; } }), { renderer } ); export function copy(node: HTMLDivElement): ActionReturn { node.addEventListener("click", handle_copy); async function handle_copy(event: MouseEvent): Promise { const path = event.composedPath() as HTMLButtonElement[]; const [copy_button] = path.filter( (e) => e?.tagName === "BUTTON" && e.classList.contains("copy_code_button") ); if (copy_button) { event.stopImmediatePropagation(); const copy_text = copy_button.parentElement!.innerText.trim(); const copy_sucess_button = Array.from( copy_button.children )[1] as HTMLDivElement; const copied = await copy_to_clipboard(copy_text); if (copied) copy_feedback(copy_sucess_button); function copy_feedback(_copy_sucess_button: HTMLDivElement): void { _copy_sucess_button.style.opacity = "1"; setTimeout(() => { _copy_sucess_button.style.opacity = "0"; }, 2000); } } } return { destroy(): void { node.removeEventListener("click", handle_copy); } }; } async function copy_to_clipboard(value: string): Promise { let copied = false; if ("clipboard" in navigator) { await navigator.clipboard.writeText(value); copied = true; } else { const textArea = document.createElement("textarea"); textArea.value = value; textArea.style.position = "absolute"; textArea.style.left = "-999999px"; document.body.prepend(textArea); textArea.select(); try { document.execCommand("copy"); copied = true; } catch (error) { console.error(error); copied = false; } finally { textArea.remove(); } } return copied; } export { marked }; export const format_chat_for_sharing = async ( chat: [string | FileData | null, string | FileData | null][] ): Promise => { let messages = await Promise.all( chat.map(async (message_pair) => { return await Promise.all( message_pair.map(async (message, i) => { if (message === null) return ""; let speaker_emoji = i === 0 ? "😃" : "🤖"; let html_content = ""; if (typeof message === "string") { const regexPatterns = { audio: /|!\[.*?\]\((\/file=.*?)\)/g }; html_content = message; for (let [_, regex] of Object.entries(regexPatterns)) { let match; while ((match = regex.exec(message)) !== null) { const fileUrl = match[1] || match[2]; const newUrl = await uploadToHuggingFace(fileUrl, "url"); html_content = html_content.replace(fileUrl, newUrl); } } } else { const file_url = await uploadToHuggingFace(message.data, "url"); if (message.mime_type?.includes("audio")) { html_content = ``; } else if (message.mime_type?.includes("video")) { html_content = file_url; } else if (message.mime_type?.includes("image")) { html_content = ``; } } return `${speaker_emoji}: ${html_content}`; }) ); }) ); return messages .map((message_pair) => message_pair.join( message_pair[0] !== "" && message_pair[1] !== "" ? "\n" : "" ) ) .join("\n"); };