1<script lang="ts">
  2	import { Card } from '$lib/components/ui/card';
  3	import { ChatAttachmentsList, MarkdownContent } from '$lib/components/app';
  4	import { config } from '$lib/stores/settings.svelte';
  5	import ChatMessageActions from './ChatMessageActions.svelte';
  6	import ChatMessageEditForm from './ChatMessageEditForm.svelte';
  7
  8	interface Props {
  9		class?: string;
 10		message: DatabaseMessage;
 11		isEditing: boolean;
 12		editedContent: string;
 13		editedExtras?: DatabaseMessageExtra[];
 14		editedUploadedFiles?: ChatUploadedFile[];
 15		siblingInfo?: ChatMessageSiblingInfo | null;
 16		showDeleteDialog: boolean;
 17		deletionInfo: {
 18			totalCount: number;
 19			userMessages: number;
 20			assistantMessages: number;
 21			messageTypes: string[];
 22		} | null;
 23		onCancelEdit: () => void;
 24		onSaveEdit: () => void;
 25		onSaveEditOnly?: () => void;
 26		onEditKeydown: (event: KeyboardEvent) => void;
 27		onEditedContentChange: (content: string) => void;
 28		onEditedExtrasChange?: (extras: DatabaseMessageExtra[]) => void;
 29		onEditedUploadedFilesChange?: (files: ChatUploadedFile[]) => void;
 30		onCopy: () => void;
 31		onEdit: () => void;
 32		onDelete: () => void;
 33		onConfirmDelete: () => void;
 34		onNavigateToSibling?: (siblingId: string) => void;
 35		onShowDeleteDialogChange: (show: boolean) => void;
 36		textareaElement?: HTMLTextAreaElement;
 37	}
 38
 39	let {
 40		class: className = '',
 41		message,
 42		isEditing,
 43		editedContent,
 44		editedExtras = [],
 45		editedUploadedFiles = [],
 46		siblingInfo = null,
 47		showDeleteDialog,
 48		deletionInfo,
 49		onCancelEdit,
 50		onSaveEdit,
 51		onSaveEditOnly,
 52		onEditKeydown,
 53		onEditedContentChange,
 54		onEditedExtrasChange,
 55		onEditedUploadedFilesChange,
 56		onCopy,
 57		onEdit,
 58		onDelete,
 59		onConfirmDelete,
 60		onNavigateToSibling,
 61		onShowDeleteDialogChange,
 62		textareaElement = $bindable()
 63	}: Props = $props();
 64
 65	let isMultiline = $state(false);
 66	let messageElement: HTMLElement | undefined = $state();
 67	const currentConfig = config();
 68
 69	$effect(() => {
 70		if (!messageElement || !message.content.trim()) return;
 71
 72		if (message.content.includes('\n')) {
 73			isMultiline = true;
 74			return;
 75		}
 76
 77		const resizeObserver = new ResizeObserver((entries) => {
 78			for (const entry of entries) {
 79				const element = entry.target as HTMLElement;
 80				const estimatedSingleLineHeight = 24; // Typical line height for text-md
 81
 82				isMultiline = element.offsetHeight > estimatedSingleLineHeight * 1.5;
 83			}
 84		});
 85
 86		resizeObserver.observe(messageElement);
 87
 88		return () => {
 89			resizeObserver.disconnect();
 90		};
 91	});
 92</script>
 93
 94<div
 95	aria-label="User message with actions"
 96	class="group flex flex-col items-end gap-3 md:gap-2 {className}"
 97	role="group"
 98>
 99	{#if isEditing}
100		<ChatMessageEditForm
101			bind:textareaElement
102			messageId={message.id}
103			{editedContent}
104			{editedExtras}
105			{editedUploadedFiles}
106			originalContent={message.content}
107			originalExtras={message.extra}
108			showSaveOnlyOption={!!onSaveEditOnly}
109			{onCancelEdit}
110			{onSaveEdit}
111			{onSaveEditOnly}
112			{onEditKeydown}
113			{onEditedContentChange}
114			{onEditedExtrasChange}
115			{onEditedUploadedFilesChange}
116		/>
117	{:else}
118		{#if message.extra && message.extra.length > 0}
119			<div class="mb-2 max-w-[80%]">
120				<ChatAttachmentsList attachments={message.extra} readonly={true} imageHeight="h-80" />
121			</div>
122		{/if}
123
124		{#if message.content.trim()}
125			<Card
126				class="max-w-[80%] rounded-[1.125rem] border-none bg-primary px-3.75 py-1.5 text-primary-foreground data-[multiline]:py-2.5"
127				data-multiline={isMultiline ? '' : undefined}
128			>
129				{#if currentConfig.renderUserContentAsMarkdown}
130					<div bind:this={messageElement} class="text-md">
131						<MarkdownContent
132							class="markdown-user-content text-primary-foreground"
133							content={message.content}
134						/>
135					</div>
136				{:else}
137					<span bind:this={messageElement} class="text-md whitespace-pre-wrap">
138						{message.content}
139					</span>
140				{/if}
141			</Card>
142		{/if}
143
144		{#if message.timestamp}
145			<div class="max-w-[80%]">
146				<ChatMessageActions
147					actionsPosition="right"
148					{deletionInfo}
149					justify="end"
150					{onConfirmDelete}
151					{onCopy}
152					{onDelete}
153					{onEdit}
154					{onNavigateToSibling}
155					{onShowDeleteDialogChange}
156					{siblingInfo}
157					{showDeleteDialog}
158					role="user"
159				/>
160			</div>
161		{/if}
162	{/if}
163</div>