Files
windmill/frontend/src/lib/components/copilot/chat/AIChatMessage.svelte
claude[bot] cb4d57cb62 feat: allow editing messages in AI chat (#6117)
* feat: add message editing functionality to AI chat

- Add editMessage() method to AIChatManager for editing user messages
- Add edit button UI that appears on hover for all user messages  
- Transform message display into textarea when editing
- Remove subsequent messages and resend on Enter
- Add keyboard shortcuts: Enter to save, Escape to cancel
- Maintain existing restart generation functionality

Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>

* fix

* big cleaning

* use aichatinput when editing message

* cleaning

* fix

* remove logs

* better retry button

* fix

* Update frontend/src/lib/components/copilot/chat/ContextTextarea.svelte

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* cleaning

* fix retry logic

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>
Co-authored-by: centdix <farhadg110@gmail.com>
Co-authored-by: centdix <40307056+centdix@users.noreply.github.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-07-04 13:11:29 +02:00

113 lines
3.0 KiB
Svelte

<script lang="ts">
import { twMerge } from 'tailwind-merge'
import type { DisplayMessage } from './shared'
import ContextElementBadge from './ContextElementBadge.svelte'
import AssistantMessage from './AssistantMessage.svelte'
import { aiChatManager } from './AIChatManager.svelte'
import { Button } from '$lib/components/common'
import { RefreshCwIcon, Undo2Icon } from 'lucide-svelte'
import AIChatInput from './AIChatInput.svelte'
import type { ContextElement } from './context'
interface Props {
availableContext: ContextElement[]
selectedContext: ContextElement[]
message: DisplayMessage
messageIndex: number
editingMessageIndex: number | null
}
let {
message,
messageIndex,
availableContext,
selectedContext = $bindable(),
editingMessageIndex = $bindable(null)
}: Props = $props()
function editMessage() {
if (message.role !== 'user' || editingMessageIndex !== null || aiChatManager.loading) {
return
}
editingMessageIndex = messageIndex
}
</script>
<div
class={twMerge(
message.role === 'user' && messageIndex > 0 && 'mt-6',
'mb-2',
message.role !== 'user' ? 'cursor-default' : 'cursor-pointer'
)}
role="button"
tabindex="0"
onclick={() => editMessage()}
onkeydown={() => {}}
>
{#if message.role === 'user' && message.contextElements && editingMessageIndex !== messageIndex}
<div class="flex flex-row gap-1 mb-1 overflow-scroll no-scrollbar px-2">
{#each message.contextElements as element}
<ContextElementBadge contextElement={element} />
{/each}
</div>
{/if}
{#if message.role === 'user' && editingMessageIndex === messageIndex}
<AIChatInput
{availableContext}
bind:selectedContext
initialInstructions={message.content}
{editingMessageIndex}
onEditEnd={() => (editingMessageIndex = null)}
/>
{:else}
<div
class={twMerge(
'text-sm py-1 mx-2',
message.role === 'user' &&
'px-2 border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-900 rounded-lg relative group',
(message.role === 'assistant' || message.role === 'tool') && 'px-[1px]',
message.role === 'tool' && 'text-tertiary'
)}
>
{#if message.role === 'assistant'}
<AssistantMessage {message} />
{:else}
{message.content}
{/if}
</div>
{/if}
{#if message.role === 'user' && message.snapshot}
<div class="mx-2 text-sm text-tertiary flex flex-row items-center justify-between gap-2 mt-2">
Saved a flow snapshot
<Button
size="xs2"
variant="border"
color="light"
on:click={() => {
if (message.snapshot) {
aiChatManager.flowAiChatHelpers?.revertToSnapshot(message.snapshot)
}
}}
title="Revert to snapshot"
startIcon={{ icon: Undo2Icon }}
>
Revert
</Button>
</div>
{/if}
</div>
{#if message.role === 'user' && message.error}
<div class="flex justify-end px-2 -mt-1">
<Button
size="xs2"
variant="border"
title="Retry generation"
color="light"
startIcon={{ icon: RefreshCwIcon }}
onclick={() => aiChatManager.retryRequest(messageIndex)}
>
Retry
</Button>
</div>
{/if}