File size: 2,752 Bytes
502cb81
5851d63
4b8b411
5851d63
4b8b411
b2170a7
60216ec
f48efbb
502cb81
5851d63
 
 
 
 
 
502cb81
5851d63
25a5986
5851d63
 
 
502cb81
5851d63
de2ec19
502cb81
 
25a5986
502cb81
 
 
 
5851d63
f2e5687
25a5986
 
 
502cb81
5851d63
5c869f5
5851d63
 
 
 
 
 
 
b2170a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502cb81
5213b80
502cb81
73b6f4f
92f7aa1
 
d5e14b5
5213b80
5851d63
25a5986
 
 
 
 
 
502cb81
5213b80
f48efbb
 
 
 
 
 
 
 
5213b80
 
502cb81
eeca96c
5851d63
5213b80
502cb81
8c5a2cf
64cfbce
 
 
b2170a7
5213b80
502cb81
5213b80
25c63d0
5213b80
 
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
99
100
101
102
103
104
105
106
107
<script lang="ts">
	import { run } from "svelte/legacy";

	import type { Conversation } from "$lib/types.js";

	import IconPlus from "~icons/carbon/add";
	import CodeSnippets from "./InferencePlaygroundCodeSnippets.svelte";
	import Message from "./message.svelte";

	interface Props {
		conversation: Conversation;
		loading: boolean;
		viewCode: boolean;
		compareActive: boolean;
	}

	let { conversation = $bindable(), loading, viewCode, compareActive }: Props = $props();

	let shouldScrollToBottom = $state(true);
	let isProgrammaticScroll = $state(true);
	let conversationLength = $state(conversation.messages.length);

	let messageContainer: HTMLDivElement | null = $state(null);

	function scrollToBottom() {
		if (messageContainer) {
			isProgrammaticScroll = true;
			messageContainer.scrollTop = messageContainer.scrollHeight;
		}
	}

	run(() => {
		if (conversation.messages.at(-1)) {
			if (shouldScrollToBottom) {
				scrollToBottom();
			}
		}
	});

	run(() => {
		if (conversation.messages.length !== conversationLength) {
			// enable automatic scrolling when new message was added
			conversationLength = conversation.messages.length;
			shouldScrollToBottom = true;
		}
	});

	function addMessage() {
		const msgs = conversation.messages.slice();
		conversation.messages = [
			...msgs,
			{
				role: msgs.at(-1)?.role === "user" ? "assistant" : "user",
				content: "",
			},
		];
		conversation = conversation;
	}

	function deleteMessage(idx: number) {
		conversation.messages.splice(idx, 1);
		conversation = conversation;
	}
</script>

<div
	class="@container flex flex-col overflow-x-hidden overflow-y-auto {compareActive
		? 'max-h-[calc(100dvh-5.8rem-2.5rem-75px)] md:max-h-[calc(100dvh-5.8rem-2.5rem)]'
		: 'max-h-[calc(100dvh-5.8rem-2.5rem-75px)] md:max-h-[calc(100dvh-5.8rem)]'}"
	class:animate-pulse={loading && !conversation.streaming}
	bind:this={messageContainer}
	onscroll={() => {
		// disable automatic scrolling is user initiates scroll
		if (!isProgrammaticScroll) {
			shouldScrollToBottom = false;
		}
		isProgrammaticScroll = false;
	}}
>
	{#if !viewCode}
		{#each conversation.messages as _msg, idx}
			<Message
				bind:content={conversation.messages[idx]!.content}
				role={conversation.messages[idx]!.role}
				autofocus={idx === conversation.messages.length - 1}
				{loading}
				onDelete={() => deleteMessage(idx)}
			/>
		{/each}

		<button
			class="flex px-3.5 py-6 hover:bg-gray-50 md:px-6 dark:hover:bg-gray-800/50"
			onclick={addMessage}
			disabled={loading}
		>
			<div class="flex items-center gap-2 p-0! text-sm font-semibold">
				<div class="text-lg">
					<IconPlus />
				</div>
				Add message
			</div>
		</button>
	{:else}
		<CodeSnippets {conversation} on:closeCode />
	{/if}
</div>