File size: 2,997 Bytes
2c972ff
98051f8
 
 
248ad09
ace9a3e
98051f8
 
8cbfe1a
3c71c00
2328f1f
 
 
 
 
6385f42
 
 
3c71c00
 
8cbfe1a
949cae0
 
 
 
 
ace9a3e
6385f42
 
 
 
 
 
 
 
ace9a3e
 
98051f8
6385f42
ace9a3e
 
d852bfd
949cae0
 
 
 
 
 
 
 
 
 
 
98051f8
949cae0
 
 
 
 
2c972ff
 
98051f8
2c972ff
 
 
 
 
 
 
ab6b390
2c972ff
949cae0
 
 
 
746ce50
949cae0
 
 
98051f8
6385f42
949cae0
 
 
 
 
2c972ff
 
 
98051f8
746ce50
2c972ff
690e2ca
 
2c972ff
 
 
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<script lang="ts">
	import { marked } from "marked";
	import type { Message } from "$lib/types/Message";
	import { afterUpdate } from "svelte";
	import { deepestChild } from "$lib/utils/deepestChild";

	import CodeBlock from "../CodeBlock.svelte";
	import IconLoading from "../icons/IconLoading.svelte";

	function sanitizeMd(md: string) {
		return md
			.replaceAll(/^(<\|startoftext\|>|<\|startoftext\|$|<\|startoftext$|<\|$|<$)/g, "")
			.replaceAll(/(<\|endoftext\|>|<\|endoftext\|$|<\|endoftext$)/g, "")
			.replaceAll("&", "&amp;")
			.replaceAll("<", "&lt;");
	}
	function unsanitizeMd(md: string) {
		return md.replaceAll("&lt;", "<").replaceAll("&amp;", "&");
	}

	export let message: Message;
	export let loading: boolean = false;

	let contentEl: HTMLElement;
	let loadingEl: any;
	let pendingTimeout: NodeJS.Timeout;

	const renderer = new marked.Renderer();

	// For code blocks with simple backticks
	renderer.codespan = (code) => {
		// Unsanitize double-sanitized code
		return `<code>${code.replaceAll("&amp;", "&")}</code>`;
	};

	const options: marked.MarkedOptions = {
		...marked.getDefaults(),
		gfm: true,
		renderer,
	};

	$: 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 prose-pre:my-2 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 max-sm:prose-sm dark:prose-invert prose-pre:bg-gray-800 dark:prose-pre:bg-gray-900 prose-h1:text-lg prose-h2:text-base prose-h3:text-base prose-headings:font-semibold max-w-none"
				bind:this={contentEl}
			>
				{#each tokens as token}
					{#if token.type === "code"}
						<CodeBlock lang={token.lang} code={unsanitizeMd(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 max-sm:text-sm">
		<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}