File size: 2,310 Bytes
a3ae6ee c3aa4e3 fc15a4c 3aa8136 8811ee0 697285c 3aa8136 bd8c7a0 0ca99f1 bd8c7a0 3aa8136 8811ee0 697285c 8811ee0 697285c 3aa8136 a3ae6ee 1b66f8d a3ae6ee 3aa8136 a3ae6ee 3aa8136 a3ae6ee 7b1d57f a3ae6ee |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
<script lang="ts">
import { marked } from 'marked';
import type { Message } from '$lib/types/Message';
import { afterUpdate } from 'svelte';
import { deepestChild } from '$lib/utils/dom';
import CodeBlock from '../CodeBlock.svelte';
import IconLoading from '../icons/IconLoading.svelte';
function sanitizeMd(md: string) {
return md.replaceAll('<', '<');
}
export let message: Message;
export let loading: boolean = false;
let contentEl: HTMLElement;
let loadingEl: any;
let pendingTimeout: NodeJS.Timeout;
const options: marked.MarkedOptions = {
...marked.getDefaults(),
gfm: true
};
$: tokens = marked.lexer(sanitizeMd(message.content));
afterUpdate(() => {
loadingEl?.$destroy();
clearTimeout(pendingTimeout);
// Add loading animation to the last message if update takes more than 600ms
if (loading) {
pendingTimeout = setTimeout(() => {
if (contentEl) {
loadingEl = new IconLoading({
target: deepestChild(contentEl),
props: { classNames: 'loading inline ml-2' }
});
}
}, 600);
}
});
</script>
{#if message.from === 'assistant'}
<div class="flex items-start justify-start gap-4 leading-relaxed">
<img
alt=""
src="https://huggingface.co/avatars/2edb18bd0206c16b433841a47f53fa8e.svg"
class="mt-5 w-3 h-3 flex-none rounded-full shadow-lg"
/>
<div
class="relative rounded-2xl px-5 py-3.5 border border-gray-100 bg-gradient-to-br from-gray-50 dark:from-gray-800/40 dark:border-gray-800 text-gray-600 dark:text-gray-300 min-h-[calc(2rem+theme(spacing[3.5])*2)] min-w-[100px]"
>
{#if !message.content}
<IconLoading classNames="absolute inset-0 m-auto" />
{/if}
<div
class="prose dark:prose-invert :prose-pre:bg-gray-100 dark:prose-pre:bg-gray-950"
bind:this={contentEl}
>
{#each tokens as token}
{#if token.type === 'code'}
<CodeBlock lang={token.lang} code={token.text} />
{:else}
{@html marked.parser([token], options)}
{/if}
{/each}
</div>
</div>
</div>
{/if}
{#if message.from === 'user'}
<div class="flex items-start justify-start gap-4">
<div class="mt-5 w-3 h-3 flex-none rounded-full" />
<div class="rounded-2xl px-5 py-3.5 text-gray-500 dark:text-gray-400 whitespace-break-spaces">
{message.content.trim()}
</div>
</div>
{/if}
|