2 import { Check, X } from '@lucide/svelte';
3 import { ChatMessageActionIcons, MarkdownContent } from '$lib/components/app';
4 import { Button } from '$lib/components/ui/button';
5 import { Card } from '$lib/components/ui/card';
6 import { INPUT_CLASSES } from '$lib/constants';
7 import { getMessageEditContext } from '$lib/contexts';
8 import { KeyboardKey, MessageRole } from '$lib/enums';
9 import { config } from '$lib/stores/settings.svelte';
10 import { isIMEComposing } from '$lib/utils';
14 message: DatabaseMessage;
15 siblingInfo?: ChatMessageSiblingInfo | null;
16 showDeleteDialog: boolean;
20 assistantMessages: number;
21 messageTypes: string[];
26 onConfirmDelete: () => void;
27 onNavigateToSibling?: (siblingId: string) => void;
28 onShowDeleteDialogChange: (show: boolean) => void;
29 textareaElement?: HTMLTextAreaElement;
33 class: className = '',
43 onShowDeleteDialogChange,
44 textareaElement = $bindable()
47 const editCtx = getMessageEditContext();
49 function handleEditKeydown(event: KeyboardEvent) {
50 if (event.key === KeyboardKey.ENTER && !event.shiftKey && !isIMEComposing(event)) {
51 event.preventDefault();
54 } else if (event.key === KeyboardKey.ESCAPE) {
55 event.preventDefault();
61 let isMultiline = $state(false);
62 let messageElement: HTMLElement | undefined = $state();
63 let isExpanded = $state(false);
64 let contentHeight = $state(0);
66 const MAX_HEIGHT = 200; // pixels
67 const currentConfig = config();
69 let showExpandButton = $derived(contentHeight > MAX_HEIGHT);
72 if (!messageElement || !message.content.trim()) return;
74 if (message.content.includes('\n')) {
78 const resizeObserver = new ResizeObserver((entries) => {
79 for (const entry of entries) {
80 const element = entry.target as HTMLElement;
81 const estimatedSingleLineHeight = 24;
83 isMultiline = element.offsetHeight > estimatedSingleLineHeight * 1.5;
84 contentHeight = element.scrollHeight;
88 resizeObserver.observe(messageElement);
91 resizeObserver.disconnect();
95 function toggleExpand() {
96 isExpanded = !isExpanded;
101 aria-label="System message with actions"
102 class="group flex flex-col items-end gap-3 md:gap-2 {className}"
105 {#if editCtx.isEditing}
106 <div class="w-full max-w-[80%]">
108 bind:this={textareaElement}
109 value={editCtx.editedContent}
110 class="min-h-[60px] w-full resize-none rounded-2xl px-3 py-2 text-sm {INPUT_CLASSES}"
111 onkeydown={handleEditKeydown}
112 oninput={(e) => editCtx.setContent(e.currentTarget.value)}
113 placeholder="Edit system message..."
116 <div class="mt-2 flex justify-end gap-2">
117 <Button class="h-8 px-3" onclick={editCtx.cancel} size="sm" variant="outline">
118 <X class="mr-1 h-3 w-3" />
125 onclick={editCtx.save}
126 disabled={!editCtx.editedContent.trim()}
129 <Check class="mr-1 h-3 w-3" />
136 {#if message.content.trim()}
137 <div class="relative max-w-[80%]">
139 class="group/expand w-full text-left {!isExpanded && showExpandButton
142 onclick={showExpandButton && !isExpanded ? toggleExpand : undefined}
146 class="overflow-y-auto rounded-[1.125rem] !border-2 !border-dashed !border-border/50 bg-muted px-3.75 py-1.5 data-[multiline]:py-2.5"
147 data-multiline={isMultiline ? '' : undefined}
148 style="border: 2px dashed hsl(var(--border)); max-height: var(--max-message-height); overflow-wrap: anywhere; word-break: break-word;"
151 class="relative transition-all duration-300 {isExpanded
152 ? 'cursor-text select-text'
154 style={!isExpanded && showExpandButton
155 ? `max-height: ${MAX_HEIGHT}px;`
156 : 'max-height: none;'}
158 {#if currentConfig.renderUserContentAsMarkdown}
159 <div bind:this={messageElement} class={isExpanded ? 'cursor-text' : ''}>
161 class="markdown-system-content -my-4"
162 content={message.content}
167 bind:this={messageElement}
168 class="text-md whitespace-pre-wrap {isExpanded ? 'cursor-text' : ''}"
174 {#if !isExpanded && showExpandButton}
176 class="pointer-events-none absolute right-0 bottom-0 left-0 h-48 bg-gradient-to-t from-muted to-transparent"
180 class="pointer-events-none absolute right-0 bottom-4 left-0 flex justify-center opacity-0 transition-opacity group-hover/expand:opacity-100"
183 class="rounded-full px-4 py-1.5 text-xs shadow-md"
187 Show full system message
193 {#if isExpanded && showExpandButton}
194 <div class="mb-2 flex justify-center">
196 class="rounded-full px-4 py-1.5 text-xs"
204 Collapse System Message
213 {#if message.timestamp}
214 <div class="max-w-[80%]">
215 <ChatMessageActionIcons
216 actionsPosition="right"
223 {onNavigateToSibling}
224 {onShowDeleteDialogChange}
227 role={MessageRole.USER}