<script lang="ts">
import { Card } from '$lib/components/ui/card';
import { ChatAttachmentsList, MarkdownContent } from '$lib/components/app';
import { getMessageEditContext } from '$lib/contexts';
import { config } from '$lib/stores/settings.svelte';
import ChatMessageActions from './ChatMessageActions.svelte';
import ChatMessageEditForm from './ChatMessageEditForm.svelte';
import { MessageRole } from '$lib/enums';
interface Props {
class?: string;
message: DatabaseMessage;
siblingInfo?: ChatMessageSiblingInfo | null;
deletionInfo: {
totalCount: number;
userMessages: number;
assistantMessages: number;
messageTypes: string[];
} | null;
showDeleteDialog: boolean;
onEdit: () => void;
onDelete: () => void;
onConfirmDelete: () => void;
onForkConversation?: (options: { name: string; includeAttachments: boolean }) => void;
onShowDeleteDialogChange: (show: boolean) => void;
onNavigateToSibling?: (siblingId: string) => void;
onCopy: () => void;
}
let {
class: className = '',
message,
siblingInfo = null,
deletionInfo,
showDeleteDialog,
onEdit,
onDelete,
onConfirmDelete,
onForkConversation,
onShowDeleteDialogChange,
onNavigateToSibling,
onCopy
}: Props = $props();
// Get contexts
const editCtx = getMessageEditContext();
let isMultiline = $state(false);
let messageElement: HTMLElement | undefined = $state();
const currentConfig = config();
$effect(() => {
if (!messageElement || !message.content.trim()) return;
if (message.content.includes('\n')) {
isMultiline = true;
return;
}
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const element = entry.target as HTMLElement;
const estimatedSingleLineHeight = 24; // Typical line height for text-md
isMultiline = element.offsetHeight > estimatedSingleLineHeight * 1.5;
}
});
resizeObserver.observe(messageElement);
return () => {
resizeObserver.disconnect();
};
});
</script>
<div
aria-label="User message with actions"
class="group flex flex-col items-end gap-3 md:gap-2 {className}"
role="group"
>
{#if editCtx.isEditing}
<ChatMessageEditForm />
{:else}
{#if message.extra && message.extra.length > 0}
<div class="mb-2 max-w-[80%]">
<ChatAttachmentsList attachments={message.extra} readonly imageHeight="h-80" />
</div>
{/if}
{#if message.content.trim()}
<Card
class="max-w-[80%] overflow-y-auto rounded-[1.125rem] border-none bg-primary/5 px-3.75 py-1.5 text-foreground backdrop-blur-md data-[multiline]:py-2.5 dark:bg-primary/15"
data-multiline={isMultiline ? '' : undefined}
style="max-height: var(--max-message-height); overflow-wrap: anywhere; word-break: break-word;"
>
{#if currentConfig.renderUserContentAsMarkdown}
<div bind:this={messageElement}>
<MarkdownContent class="markdown-user-content -my-4" content={message.content} />
</div>
{:else}
<span bind:this={messageElement} class="text-md whitespace-pre-wrap">
{message.content}
</span>
{/if}
</Card>
{/if}
{#if message.timestamp}
<div class="max-w-[80%]">
<ChatMessageActions
actionsPosition="right"
{deletionInfo}
justify="end"
{onConfirmDelete}
{onCopy}
{onDelete}
{onEdit}
{onForkConversation}
{onNavigateToSibling}
{onShowDeleteDialogChange}
{siblingInfo}
{showDeleteDialog}
role={MessageRole.USER}
/>
</div>
{/if}
{/if}
</div>