File size: 2,176 Bytes
502cb81
97c4991
4b8b411
0ce274f
 
 
b2170a7
b1426d3
f48efbb
502cb81
5851d63
 
 
 
 
502cb81
28faefd
5851d63
2dd779d
 
 
5851d63
0ce274f
5c869f5
2dd779d
 
 
0ce274f
2dd779d
0ce274f
 
 
 
 
 
 
5851d63
2dd779d
b2170a7
 
 
 
 
 
 
 
 
 
 
 
 
 
9b4caaa
b2170a7
502cb81
5213b80
502cb81
28faefd
d5e14b5
5213b80
2dd779d
502cb81
5213b80
f48efbb
 
899d9c6
 
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
<script lang="ts">
	import { type Conversation } from "$lib/types.js";

	import { ScrollState } from "$lib/spells/scroll-state.svelte";
	import { watch } from "runed";
	import { tick } from "svelte";
	import IconPlus from "~icons/carbon/add";
	import CodeSnippets from "./code-snippets.svelte";
	import Message from "./message.svelte";

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

	let { conversation = $bindable(), loading, viewCode }: Props = $props();
	let messageContainer: HTMLDivElement | null = $state(null);
	const scrollState = new ScrollState({
		element: () => messageContainer,
		offset: { bottom: 100 },
	});
	const atBottom = $derived(scrollState.arrived.bottom);

	watch(
		() => conversation.messages.at(-1)?.content,
		() => {
			const shouldScroll = atBottom && !scrollState.isScrolling;
			if (!shouldScroll) return;
			try {
				tick().then(() => {
					scrollState.scrollToBottom();
				});
			} catch {
				// noop
			}
		}
	);

	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 = conversation.messages.slice(0, idx);
	}
</script>

<div
	class="@container flex flex-col overflow-x-hidden overflow-y-auto"
	class:animate-pulse={loading && !conversation.streaming}
	bind:this={messageContainer}
	id="test-this"
>
	{#if !viewCode}
		{#each conversation.messages as _msg, idx}
			<Message
				bind:message={conversation.messages[idx]!}
				{conversation}
				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>