|
<script lang="ts"> |
|
import type { Message } from "$lib/types/Message"; |
|
import { snapScrollToBottom } from "$lib/actions/snapScrollToBottom"; |
|
import ScrollToBottomBtn from "$lib/components/ScrollToBottomBtn.svelte"; |
|
import { tick } from "svelte"; |
|
import { randomUUID } from "$lib/utils/randomUuid"; |
|
import type { Model } from "$lib/types/Model"; |
|
import ChatIntroduction from "./ChatIntroduction.svelte"; |
|
import ChatMessage from "./ChatMessage.svelte"; |
|
import type { WebSearchUpdate } from "$lib/types/MessageUpdate"; |
|
import { browser } from "$app/environment"; |
|
import SystemPromptModal from "../SystemPromptModal.svelte"; |
|
import type { Assistant } from "$lib/types/Assistant"; |
|
import AssistantIntroduction from "./AssistantIntroduction.svelte"; |
|
import { page } from "$app/stores"; |
|
import { base } from "$app/paths"; |
|
|
|
export let messages: Message[]; |
|
export let loading: boolean; |
|
export let pending: boolean; |
|
export let isAuthor: boolean; |
|
export let currentModel: Model; |
|
export let assistant: Assistant | undefined; |
|
export let models: Model[]; |
|
export let preprompt: string | undefined; |
|
export let readOnly: boolean; |
|
|
|
let chatContainer: HTMLElement; |
|
|
|
export let webSearchMessages: WebSearchUpdate[] = []; |
|
|
|
async function scrollToBottom() { |
|
await tick(); |
|
chatContainer.scrollTop = chatContainer.scrollHeight; |
|
} |
|
|
|
|
|
$: if (browser && messages[messages.length - 1]?.from === "user") { |
|
scrollToBottom(); |
|
} |
|
</script> |
|
|
|
<div |
|
class="scrollbar-custom mr-1 h-full overflow-y-auto" |
|
use:snapScrollToBottom={messages.length ? [...messages, ...webSearchMessages] : false} |
|
bind:this={chatContainer} |
|
> |
|
<div class="mx-auto flex h-full max-w-3xl flex-col gap-6 px-5 pt-6 sm:gap-8 xl:max-w-4xl"> |
|
{#each messages as message, i} |
|
{#if i === 0 && $page.data?.assistant} |
|
<a |
|
class="mx-auto flex items-center gap-1.5 rounded-full border border-gray-100 bg-gray-50 py-1 pl-1 pr-3 text-sm text-gray-800 hover:bg-gray-100 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700" |
|
href="{base}/settings/assistants/{$page.data.assistant._id}" |
|
> |
|
{#if $page.data?.assistant.avatar} |
|
<img |
|
src="{base}/settings/assistants/{$page.data?.assistant._id.toString()}/avatar?hash=${$page |
|
.data?.assistant.avatar}" |
|
alt="Avatar" |
|
class="size-5 rounded-full object-cover" |
|
/> |
|
{:else} |
|
<div |
|
class="flex size-6 items-center justify-center rounded-full bg-gray-300 font-bold uppercase text-gray-500" |
|
> |
|
{$page.data?.assistant.name[0]} |
|
</div> |
|
{/if} |
|
|
|
{$page.data.assistant.name} |
|
</a> |
|
{:else if i === 0 && preprompt && preprompt != currentModel.preprompt} |
|
<SystemPromptModal preprompt={preprompt ?? ""} /> |
|
{/if} |
|
<ChatMessage |
|
loading={loading && i === messages.length - 1} |
|
{message} |
|
{isAuthor} |
|
{readOnly} |
|
model={currentModel} |
|
webSearchMessages={i === messages.length - 1 ? webSearchMessages : []} |
|
on:retry |
|
on:vote |
|
on:continue |
|
/> |
|
{:else} |
|
{#if !assistant} |
|
<ChatIntroduction {models} {currentModel} on:message /> |
|
{:else} |
|
<AssistantIntroduction {assistant} on:message /> |
|
{/if} |
|
{/each} |
|
{#if pending && messages[messages.length - 1]?.from === "user"} |
|
<ChatMessage |
|
message={{ from: "assistant", content: "", id: randomUUID() }} |
|
model={currentModel} |
|
{webSearchMessages} |
|
/> |
|
{/if} |
|
<div class="h-44 flex-none" /> |
|
</div> |
|
<ScrollToBottomBtn |
|
class="bottom-36 right-4 max-md:hidden lg:right-10" |
|
scrollNode={chatContainer} |
|
/> |
|
</div> |
|
|