Spaces:
Running
Running
| <script lang="ts"> | |
| import { Check, PenLine, X } from '@lucide/svelte'; | |
| import SvelteMarkdown from 'svelte-markdown'; | |
| import type { ChatMessage } from '$lib/helpers/types'; | |
| import Paragraph from './markdown/Paragraph.svelte'; | |
| import Heading from './markdown/Heading.svelte'; | |
| import Code from './markdown/Code.svelte'; | |
| import Codespan from './markdown/Codespan.svelte'; | |
| import Blockquote from './markdown/Blockquote.svelte'; | |
| import List from './markdown/List.svelte'; | |
| import ListItem from './markdown/ListItem.svelte'; | |
| import Link from './markdown/Link.svelte'; | |
| import Hr from './markdown/Hr.svelte'; | |
| import Think from './markdown/Think.svelte'; | |
| import Button from '../ui/button/button.svelte'; | |
| let { | |
| message, | |
| nodeId, | |
| onEdit | |
| }: { message: ChatMessage; nodeId: string; onEdit?: (newContent: string) => void } = $props(); | |
| const renderers = { | |
| paragraph: Paragraph, | |
| heading: Heading, | |
| code: Code, | |
| codespan: Codespan, | |
| blockquote: Blockquote, | |
| list: List, | |
| listitem: ListItem, | |
| link: Link, | |
| hr: Hr | |
| }; | |
| let isEditing = $state(false); | |
| let editContent = $state(''); | |
| function handleEdit() { | |
| editContent = message.content as string; | |
| isEditing = true; | |
| } | |
| function handleSubmitEdit() { | |
| const trimmed = editContent.trim(); | |
| if (trimmed) { | |
| onEdit?.(trimmed); | |
| } | |
| isEditing = false; | |
| } | |
| function handleCancelEdit() { | |
| isEditing = false; | |
| } | |
| function handleKeydown(e: KeyboardEvent) { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| handleSubmitEdit(); | |
| } else if (e.key === 'Escape') { | |
| handleCancelEdit(); | |
| } | |
| } | |
| </script> | |
| <main | |
| class="group/message pointer-events-auto cursor-auto p-1 text-lg leading-relaxed text-accent-foreground select-text" | |
| > | |
| {#if message?.role === 'user'} | |
| {#if isEditing} | |
| <div class="w-full"> | |
| <p | |
| contenteditable="true" | |
| bind:textContent={editContent} | |
| class="w-full rounded-lg bg-accent px-2 py-1 outline-none" | |
| onkeydown={handleKeydown} | |
| ></p> | |
| <div class="mt-1.5 flex items-center justify-end gap-1"> | |
| <Button variant="transparent" size="icon-2xs" onclick={handleCancelEdit}> | |
| <X class="size-3" /> | |
| </Button> | |
| <Button variant="transparent" size="icon-2xs" onclick={handleSubmitEdit}> | |
| <Check class="size-3 text-primary" /> | |
| </Button> | |
| </div> | |
| </div> | |
| {:else} | |
| <p class="relative max-w-max pr-8"> | |
| {message.content} | |
| <Button | |
| variant="transparent" | |
| size="icon-2xs" | |
| class="absolute top-0.5 right-0 opacity-0 group-hover/message:opacity-100" | |
| onclick={handleEdit} | |
| > | |
| <PenLine class="size-3" /> | |
| </Button> | |
| </p> | |
| {/if} | |
| {:else} | |
| {#if message.reasoning} | |
| <Think | |
| isThinking={!!message.content && (message.content as string)?.trim() !== ''} | |
| content={message.reasoning as string} | |
| /> | |
| {/if} | |
| <SvelteMarkdown source={message.content} renderers={renderers as any} /> | |
| {/if} | |
| </main> | |