1<script lang="ts">
  2	import { Check, X } from '@lucide/svelte';
  3	import { Card } from '$lib/components/ui/card';
  4	import { Button } from '$lib/components/ui/button';
  5	import { MarkdownContent } from '$lib/components/app';
  6	import { INPUT_CLASSES } from '$lib/constants/input-classes';
  7	import { config } from '$lib/stores/settings.svelte';
  8	import ChatMessageActions from './ChatMessageActions.svelte';
  9
 10	interface Props {
 11		class?: string;
 12		message: DatabaseMessage;
 13		isEditing: boolean;
 14		editedContent: string;
 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		onEditKeydown: (event: KeyboardEvent) => void;
 26		onEditedContentChange: (content: string) => void;
 27		onCopy: () => void;
 28		onEdit: () => void;
 29		onDelete: () => void;
 30		onConfirmDelete: () => void;
 31		onNavigateToSibling?: (siblingId: string) => void;
 32		onShowDeleteDialogChange: (show: boolean) => void;
 33		textareaElement?: HTMLTextAreaElement;
 34	}
 35
 36	let {
 37		class: className = '',
 38		message,
 39		isEditing,
 40		editedContent,
 41		siblingInfo = null,
 42		showDeleteDialog,
 43		deletionInfo,
 44		onCancelEdit,
 45		onSaveEdit,
 46		onEditKeydown,
 47		onEditedContentChange,
 48		onCopy,
 49		onEdit,
 50		onDelete,
 51		onConfirmDelete,
 52		onNavigateToSibling,
 53		onShowDeleteDialogChange,
 54		textareaElement = $bindable()
 55	}: Props = $props();
 56
 57	let isMultiline = $state(false);
 58	let messageElement: HTMLElement | undefined = $state();
 59	let isExpanded = $state(false);
 60	let contentHeight = $state(0);
 61	const MAX_HEIGHT = 200; // pixels
 62	const currentConfig = config();
 63
 64	let showExpandButton = $derived(contentHeight > MAX_HEIGHT);
 65
 66	$effect(() => {
 67		if (!messageElement || !message.content.trim()) return;
 68
 69		if (message.content.includes('\n')) {
 70			isMultiline = true;
 71		}
 72
 73		const resizeObserver = new ResizeObserver((entries) => {
 74			for (const entry of entries) {
 75				const element = entry.target as HTMLElement;
 76				const estimatedSingleLineHeight = 24;
 77
 78				isMultiline = element.offsetHeight > estimatedSingleLineHeight * 1.5;
 79				contentHeight = element.scrollHeight;
 80			}
 81		});
 82
 83		resizeObserver.observe(messageElement);
 84
 85		return () => {
 86			resizeObserver.disconnect();
 87		};
 88	});
 89
 90	function toggleExpand() {
 91		isExpanded = !isExpanded;
 92	}
 93</script>
 94
 95<div
 96	aria-label="System message with actions"
 97	class="group flex flex-col items-end gap-3 md:gap-2 {className}"
 98	role="group"
 99>
100	{#if isEditing}
101		<div class="w-full max-w-[80%]">
102			<textarea
103				bind:this={textareaElement}
104				bind:value={editedContent}
105				class="min-h-[60px] w-full resize-none rounded-2xl px-3 py-2 text-sm {INPUT_CLASSES}"
106				onkeydown={onEditKeydown}
107				oninput={(e) => onEditedContentChange(e.currentTarget.value)}
108				placeholder="Edit system message..."
109			></textarea>
110
111			<div class="mt-2 flex justify-end gap-2">
112				<Button class="h-8 px-3" onclick={onCancelEdit} size="sm" variant="outline">
113					<X class="mr-1 h-3 w-3" />
114					Cancel
115				</Button>
116
117				<Button class="h-8 px-3" onclick={onSaveEdit} disabled={!editedContent.trim()} size="sm">
118					<Check class="mr-1 h-3 w-3" />
119					Send
120				</Button>
121			</div>
122		</div>
123	{:else}
124		{#if message.content.trim()}
125			<div class="relative max-w-[80%]">
126				<button
127					class="group/expand w-full text-left {!isExpanded && showExpandButton
128						? 'cursor-pointer'
129						: 'cursor-auto'}"
130					onclick={showExpandButton && !isExpanded ? toggleExpand : undefined}
131					type="button"
132				>
133					<Card
134						class="rounded-[1.125rem] !border-2 !border-dashed !border-border/50 bg-muted px-3.75 py-1.5 data-[multiline]:py-2.5"
135						data-multiline={isMultiline ? '' : undefined}
136						style="border: 2px dashed hsl(var(--border));"
137					>
138						<div
139							class="relative overflow-hidden transition-all duration-300 {isExpanded
140								? 'cursor-text select-text'
141								: 'select-none'}"
142							style={!isExpanded && showExpandButton
143								? `max-height: ${MAX_HEIGHT}px;`
144								: 'max-height: none;'}
145						>
146							{#if currentConfig.renderUserContentAsMarkdown}
147								<div bind:this={messageElement} class="text-md {isExpanded ? 'cursor-text' : ''}">
148									<MarkdownContent class="markdown-system-content" content={message.content} />
149								</div>
150							{:else}
151								<span
152									bind:this={messageElement}
153									class="text-md whitespace-pre-wrap {isExpanded ? 'cursor-text' : ''}"
154								>
155									{message.content}
156								</span>
157							{/if}
158
159							{#if !isExpanded && showExpandButton}
160								<div
161									class="pointer-events-none absolute right-0 bottom-0 left-0 h-48 bg-gradient-to-t from-muted to-transparent"
162								></div>
163								<div
164									class="pointer-events-none absolute right-0 bottom-4 left-0 flex justify-center opacity-0 transition-opacity group-hover/expand:opacity-100"
165								>
166									<Button
167										class="rounded-full px-4 py-1.5 text-xs shadow-md"
168										size="sm"
169										variant="outline"
170									>
171										Show full system message
172									</Button>
173								</div>
174							{/if}
175						</div>
176
177						{#if isExpanded && showExpandButton}
178							<div class="mb-2 flex justify-center">
179								<Button
180									class="rounded-full px-4 py-1.5 text-xs"
181									onclick={(e) => {
182										e.stopPropagation();
183										toggleExpand();
184									}}
185									size="sm"
186									variant="outline"
187								>
188									Collapse System Message
189								</Button>
190							</div>
191						{/if}
192					</Card>
193				</button>
194			</div>
195		{/if}
196
197		{#if message.timestamp}
198			<div class="max-w-[80%]">
199				<ChatMessageActions
200					actionsPosition="right"
201					{deletionInfo}
202					justify="end"
203					{onConfirmDelete}
204					{onCopy}
205					{onDelete}
206					{onEdit}
207					{onNavigateToSibling}
208					{onShowDeleteDialogChange}
209					{siblingInfo}
210					{showDeleteDialog}
211					role="user"
212				/>
213			</div>
214		{/if}
215	{/if}
216</div>