File size: 4,569 Bytes
d2816aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<script lang="ts">
	import { AlertTriangle, X } from '@lucide/svelte';
	import { ChatForm, DialogConfirmation } from '$lib/components/app';
	import { Button } from '$lib/components/ui/button';
	import { Switch } from '$lib/components/ui/switch';
	import { getChatMessageEditContext } from '$lib/contexts';
	import { KeyboardKey, MessageRole } from '$lib/enums';
	import { chatStore } from '$lib/stores';
	import { processFilesToChatUploaded } from '$lib/utils/browser-only';

	const editCtx = getChatMessageEditContext();

	let saveWithoutRegenerate = $state(false);
	let showDiscardDialog = $state(false);
	let branchAfterEdit = $state(false);

	let isUserMessage = $derived(editCtx.messageRole === MessageRole.USER);
	let isAssistantMessage = $derived(editCtx.messageRole === MessageRole.ASSISTANT);

	let hasUnsavedChanges = $derived.by(() => {
		if (editCtx.editedContent !== editCtx.originalContent) return true;

		if (editCtx.editedUploadedFiles.length > 0) return true;

		const extrasChanged =
			editCtx.editedExtras.length !== editCtx.originalExtras.length ||
			editCtx.editedExtras.some((extra, i) => extra !== editCtx.originalExtras[i]);

		if (extrasChanged) return true;

		return false;
	});

	let hasAttachments = $derived(
		(editCtx.editedExtras && editCtx.editedExtras.length > 0) ||
			(editCtx.editedUploadedFiles && editCtx.editedUploadedFiles.length > 0)
	);

	let canSubmit = $derived(editCtx.editedContent.trim().length > 0 || hasAttachments);

	function handleGlobalKeydown(event: KeyboardEvent) {
		if (event.key === KeyboardKey.ESCAPE) {
			event.preventDefault();
			attemptCancel();
		}
	}

	function attemptCancel() {
		if (hasUnsavedChanges) {
			showDiscardDialog = true;
		} else {
			editCtx.cancel();
		}
	}

	function handleSubmit() {
		if (!canSubmit) return;

		if (isUserMessage && saveWithoutRegenerate && editCtx.showSaveOnlyOption) {
			editCtx.saveOnly();
		} else {
			if (isAssistantMessage && editCtx.setShouldBranchAfterEdit) {
				editCtx.setShouldBranchAfterEdit(branchAfterEdit);
			}

			editCtx.save();
		}

		saveWithoutRegenerate = false;
		branchAfterEdit = false;
	}

	function handleAttachmentRemove(index: number) {
		const newExtras = [...editCtx.editedExtras];

		newExtras.splice(index, 1);
		editCtx.setExtras(newExtras);
	}

	function handleUploadedFileRemove(fileId: string) {
		const newFiles = editCtx.editedUploadedFiles.filter((f) => f.id !== fileId);

		editCtx.setUploadedFiles(newFiles);
	}

	async function handleFilesAdd(files: File[]) {
		const processed = await processFilesToChatUploaded(files);

		editCtx.setUploadedFiles([...editCtx.editedUploadedFiles, ...processed]);
	}

	$effect(() => {
		chatStore.setEditModeActive(handleFilesAdd);

		return () => {
			chatStore.clearEditMode();
		};
	});
</script>

<svelte:window onkeydown={handleGlobalKeydown} />

<div class="relative w-full max-w-[80%]">
	<ChatForm
		bind:uploadedFiles={editCtx.editedUploadedFiles}
		attachments={editCtx.editedExtras}
		onAttachmentRemove={handleAttachmentRemove}
		onFilesAdd={handleFilesAdd}
		onSubmit={handleSubmit}
		onUploadedFileRemove={handleUploadedFileRemove}
		onValueChange={editCtx.setContent}
		placeholder="Edit your message..."
		showAddButton={editCtx.messageRole === MessageRole.USER}
		showModelSelector={editCtx.messageRole === MessageRole.USER}
		value={editCtx.editedContent}
	/>
</div>

<div class="mt-2 flex w-full max-w-[80%] items-center justify-between">
	{#if isUserMessage && editCtx.showSaveOnlyOption}
		<div class="flex items-center gap-2">
			<Switch bind:checked={saveWithoutRegenerate} class="scale-75" id="save-only-switch" />

			<label class="cursor-pointer text-xs text-muted-foreground" for="save-only-switch">
				Update without re-sending
			</label>
		</div>
	{:else if isAssistantMessage}
		<div class="flex items-center gap-2">
			<Switch bind:checked={branchAfterEdit} class="scale-75" id="branch-after-edit" />

			<label class="cursor-pointer text-xs text-muted-foreground" for="branch-after-edit">
				Branch conversation after edit
			</label>
		</div>
	{:else}
		<div></div>
	{/if}

	<Button class="h-7 px-3 text-xs" onclick={attemptCancel} size="sm" variant="ghost">
		<X class="mr-1 h-3 w-3" />

		Cancel
	</Button>
</div>

<DialogConfirmation
	bind:open={showDiscardDialog}
	cancelText="Keep editing"
	confirmText="Discard"
	description="You have unsaved changes. Are you sure you want to discard them?"
	icon={AlertTriangle}
	onCancel={() => (showDiscardDialog = false)}
	onConfirm={editCtx.cancel}
	title="Discard changes?"
	variant="destructive"
/>