web / src /lib /components /chat /Messages /Markdown /MarkdownTokens.svelte
github-actions[bot]
GitHub deploy: 96c865404d36637eafadb6d2dd2365c85d452648
8437908
raw
history blame
4.98 kB
<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:code={(e) => {
dispatch('code', e.detail);
}}
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}