<script lang="ts"> import DOMPurify from 'dompurify'; import { createEventDispatcher, onMount } from 'svelte'; import { marked, type Token } from 'marked'; import { revertSanitizedResponseContent, unescapeHtml } from '$lib/utils'; import { WEBUI_BASE_URL } from '$lib/constants'; import CodeBlock from '$lib/components/chat/Messages/CodeBlock.svelte'; import MarkdownInlineTokens from '$lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte'; import KatexRenderer from './KatexRenderer.svelte'; import Collapsible from '$lib/components/common/Collapsible.svelte'; const dispatch = createEventDispatcher(); export let id: string; export let tokens: Token[]; export let top = true; export let save = false; const headerComponent = (depth: number) => { return 'h' + depth; }; </script> <!-- {JSON.stringify(tokens)} --> {#each tokens as token, tokenIdx (tokenIdx)} {#if token.type === 'hr'} <hr /> {:else if token.type === 'heading'} <svelte:element this={headerComponent(token.depth)}> <MarkdownInlineTokens id={`${id}-${tokenIdx}-h`} tokens={token.tokens} /> </svelte:element> {:else if token.type === 'code'} {#if token.raw.includes('```')} <CodeBlock id={`${id}-${tokenIdx}`} {token} lang={token?.lang ?? ''} code={revertSanitizedResponseContent(token?.text ?? '')} {save} on:save={(e) => { dispatch('update', { raw: token.raw, oldContent: token.text, newContent: e.detail }); }} /> {:else} {token.text} {/if} {:else if token.type === 'table'} <div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full"> <table class="w-full"> <thead> <tr> {#each token.header as header, headerIdx} <th style={token.align[headerIdx] ? '' : `text-align: ${token.align[headerIdx]}`}> <MarkdownInlineTokens id={`${id}-${tokenIdx}-header-${headerIdx}`} tokens={header.tokens} /> </th> {/each} </tr> </thead> <tbody> {#each token.rows as row, rowIdx} <tr> {#each row ?? [] as cell, cellIdx} <td style={token.align[cellIdx] ? '' : `text-align: ${token.align[cellIdx]}`}> <MarkdownInlineTokens id={`${id}-${tokenIdx}-row-${rowIdx}-${cellIdx}`} tokens={cell.tokens} /> </td> {/each} </tr> {/each} </tbody> </table> </div> {:else if token.type === 'blockquote'} <blockquote> <svelte:self id={`${id}-${tokenIdx}`} tokens={token.tokens} /> </blockquote> {:else if token.type === 'list'} {#if token.ordered} <ol start={token.start || 1}> {#each token.items as item, itemIdx} <li> <svelte:self id={`${id}-${tokenIdx}-${itemIdx}`} tokens={item.tokens} top={token.loose} /> </li> {/each} </ol> {:else} <ul> {#each token.items as item, itemIdx} <li> <svelte:self id={`${id}-${tokenIdx}-${itemIdx}`} tokens={item.tokens} top={token.loose} /> </li> {/each} </ul> {/if} {:else if token.type === 'details'} <Collapsible title={token.summary} className="w-fit space-y-1"> <div class=" mb-1.5" slot="content"> <svelte:self id={`${id}-${tokenIdx}-d`} tokens={marked.lexer(token.text)} /> </div> </Collapsible> {:else if token.type === 'html'} {@const html = DOMPurify.sanitize(token.text)} {#if html && html.includes('<video')} {@html html} {:else if token.text.includes(`<iframe src="${WEBUI_BASE_URL}/api/v1/files/`)} {@html `${token.text}`} {:else} {token.text} {/if} {:else if token.type === 'iframe'} <iframe src="{WEBUI_BASE_URL}/api/v1/files/{token.fileId}/content" title={token.fileId} width="100%" frameborder="0" onload="this.style.height=(this.contentWindow.document.body.scrollHeight+20)+'px';" ></iframe> {:else if token.type === 'paragraph'} <p> <MarkdownInlineTokens id={`${id}-${tokenIdx}-p`} tokens={token.tokens ?? []} /> </p> {:else if token.type === 'text'} {#if top} <p> {#if token.tokens} <MarkdownInlineTokens id={`${id}-${tokenIdx}-t`} tokens={token.tokens} /> {:else} {unescapeHtml(token.text)} {/if} </p> {:else if token.tokens} <MarkdownInlineTokens id={`${id}-${tokenIdx}-p`} tokens={token.tokens ?? []} /> {:else} {unescapeHtml(token.text)} {/if} {:else if token.type === 'inlineKatex'} {#if token.text} <KatexRenderer content={revertSanitizedResponseContent(token.text)} displayMode={token?.displayMode ?? false} /> {/if} {:else if token.type === 'blockKatex'} {#if token.text} <KatexRenderer content={revertSanitizedResponseContent(token.text)} displayMode={token?.displayMode ?? false} /> {/if} {:else if token.type === 'space'} <div class="my-2" /> {:else} {console.log('Unknown token', token)} {/if} {/each}