|
<script lang="ts"> |
|
import type { Message } from "$lib/types/Message"; |
|
import { snapScrollToBottom } from "$lib/actions/snapScrollToBottom"; |
|
import ScrollToBottomBtn from "$lib/components/ScrollToBottomBtn.svelte"; |
|
import { createEventDispatcher, tick } from "svelte"; |
|
|
|
import ChatIntroduction from "./ChatIntroduction.svelte"; |
|
import ChatMessage from "./ChatMessage.svelte"; |
|
import { randomUUID } from "$lib/utils/randomUuid"; |
|
|
|
const dispatch = createEventDispatcher<{ retry: { id: Message["id"]; content: string } }>(); |
|
|
|
export let messages: Message[]; |
|
export let loading: boolean; |
|
export let pending: boolean; |
|
|
|
let chatContainer: HTMLElement; |
|
|
|
async function scrollToBottom() { |
|
await tick(); |
|
chatContainer.scrollTop = chatContainer.scrollHeight; |
|
} |
|
|
|
|
|
$: if (messages[messages.length - 1]?.from === "user") { |
|
scrollToBottom(); |
|
} |
|
</script> |
|
|
|
<div |
|
class="scrollbar-custom mr-1 h-full overflow-y-auto" |
|
use:snapScrollToBottom={messages.length ? messages : false} |
|
bind:this={chatContainer} |
|
> |
|
<div class="mx-auto flex h-full max-w-3xl flex-col gap-5 px-5 pt-6 sm:gap-8 xl:max-w-4xl"> |
|
{#each messages as message, i} |
|
<ChatMessage |
|
loading={loading && i === messages.length - 1} |
|
{message} |
|
on:retry={() => dispatch("retry", { id: message.id, content: message.content })} |
|
/> |
|
{:else} |
|
<ChatIntroduction on:message /> |
|
{/each} |
|
{#if pending} |
|
<ChatMessage message={{ from: "assistant", content: "", id: randomUUID() }} /> |
|
{/if} |
|
<div class="h-32 flex-none" /> |
|
</div> |
|
<ScrollToBottomBtn |
|
class="bottom-36 right-4 max-md:hidden lg:right-10" |
|
scrollNode={chatContainer} |
|
/> |
|
</div> |
|
|