File size: 3,990 Bytes
502cb81
5851d63
4b8b411
5851d63
4b8b411
b2170a7
60216ec
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
5851d63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<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";

	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}
			<div
				class=" group/message group grid grid-cols-[1fr_2.5rem] items-start gap-2 border-b px-3.5 pt-4 pb-6 hover:bg-gray-100/70 @-2xl:grid-cols-[130px_1fr_2.5rem] @2xl:grid-rows-1 @2xl:gap-4 @2xl:px-6 dark:border-gray-800 dark:hover:bg-gray-800/30"
				class:pointer-events-none={loading}
			>
				<div class="col-span-2 pt-3 pb-1 text-sm font-semibold uppercase @2xl:col-span-1 @2xl:pb-2">
					{msg.role}
				</div>
				<!-- svelte-ignore a11y_autofocus -->
				<!-- svelte-ignore a11y_positive_tabindex -->
				<textarea
					autofocus={idx === conversation.messages.length - 1}
					bind:value={conversation.messages[idx]!.content}
					placeholder="Enter {msg.role} message"
					class="resize-none overflow-hidden rounded-sm bg-transparent px-2 py-2.5 ring-gray-100 outline-none group-hover/message:ring-3 hover:resize-y hover:bg-white focus:resize-y focus:bg-white focus:ring-3 @2xl:px-3 dark:ring-gray-600 dark:hover:bg-gray-900 dark:focus:bg-gray-900"
					rows="1"
					tabindex="2"
				></textarea>
				<button
					tabindex="0"
					onclick={() => deleteMessage(idx)}
					type="button"
					class="mt-1.5 size-8 rounded-lg border border-gray-200 bg-white text-xs font-medium text-gray-900 group-hover/message:block hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 focus:outline-hidden sm:hidden dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
					>✕</button
				>
			</div>
		{/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>