UserMessage.svelte 11.1 KB
Newer Older
1
<script lang="ts">
Timothy J. Baek's avatar
Timothy J. Baek committed
2
3
	import dayjs from 'dayjs';

4
	import { tick, createEventDispatcher, getContext } from 'svelte';
5
6
	import Name from './Name.svelte';
	import ProfileImage from './ProfileImage.svelte';
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
7
	import { modelfiles, settings } from '$lib/stores';
Timothy J. Baek's avatar
Timothy J. Baek committed
8
	import Tooltip from '$lib/components/common/Tooltip.svelte';
9

10
11
	const i18n = getContext('i18n');

12
13
	const dispatch = createEventDispatcher();

14
15
16
	export let user;
	export let message;
	export let siblings;
17
	export let isFirstMessage: boolean;
18
	export let readOnly: boolean;
19
20
21
22
23
24
25
26

	export let confirmEditMessage: Function;
	export let showPreviousMessage: Function;
	export let showNextMessage: Function;
	export let copyToClipboard: Function;

	let edit = false;
	let editedContent = '';
27
	let messageEditTextAreaElement: HTMLTextAreaElement;
28
29
30
31
32
33
	const editMessageHandler = async () => {
		edit = true;
		editedContent = message.content;

		await tick();

34
35
		messageEditTextAreaElement.style.height = '';
		messageEditTextAreaElement.style.height = `${messageEditTextAreaElement.scrollHeight}px`;
36

37
		messageEditTextAreaElement?.focus();
38
39
40
41
42
43
44
45
46
47
48
49
50
	};

	const editMessageConfirmHandler = async () => {
		confirmEditMessage(message.id, editedContent);

		edit = false;
		editedContent = '';
	};

	const cancelEditMessage = () => {
		edit = false;
		editedContent = '';
	};
51
52
53
54

	const deleteMessageHandler = async () => {
		dispatch('delete', message.id);
	};
55
56
57
58
59
</script>

<div class=" flex w-full">
	<div class="w-full overflow-hidden">
		<div
Timothy J. Baek's avatar
Timothy J. Baek committed
60
			class="prose chat-{message.role} w-full max-w-full flex flex-col justify-end dark:prose-invert prose-headings:my-0 prose-p:my-0 prose-p:-mb-4 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-6 prose-li:-mb-4 whitespace-pre-line"
61
62
		>
			{#if message.files}
Timothy J. Baek's avatar
Timothy J. Baek committed
63
				<div class="mt-2.5 mb-1 w-full flex justify-end overflow-x-auto gap-2 flex-wrap">
64
65
66
67
					{#each message.files as file}
						<div>
							{#if file.type === 'image'}
								<img src={file.url} alt="input" class=" max-h-96 rounded-lg" draggable="false" />
Timothy J. Baek's avatar
Timothy J. Baek committed
68
							{:else if file.type === 'doc'}
Timothy J. Baek's avatar
Timothy J. Baek committed
69
								<button
Timothy J. Baek's avatar
Timothy J. Baek committed
70
									class="h-16 w-72 flex items-center space-x-3 px-2.5 dark:bg-gray-850 rounded-xl border border-gray-200 dark:border-none text-left"
Timothy J. Baek's avatar
Timothy J. Baek committed
71
72
73
74
75
76
									type="button"
									on:click={() => {
										if (file?.url) {
											window.open(file?.url, '_blank').focus();
										}
									}}
Timothy J. Baek's avatar
Timothy J. Baek committed
77
								>
Timothy J. Baek's avatar
Timothy J. Baek committed
78
									<div class="p-2.5 bg-red-400 text-white rounded-lg">
Timothy J. Baek's avatar
Timothy J. Baek committed
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
										<svg
											xmlns="http://www.w3.org/2000/svg"
											viewBox="0 0 24 24"
											fill="currentColor"
											class="w-6 h-6"
										>
											<path
												fill-rule="evenodd"
												d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
												clip-rule="evenodd"
											/>
											<path
												d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
											/>
										</svg>
									</div>

									<div class="flex flex-col justify-center -space-y-0.5">
Timothy J. Baek's avatar
Timothy J. Baek committed
97
										<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
Timothy J. Baek's avatar
Timothy J. Baek committed
98
99
100
											{file.name}
										</div>

101
										<div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
102
									</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
103
								</button>
104
105
							{:else if file.type === 'collection'}
								<button
Timothy J. Baek's avatar
Timothy J. Baek committed
106
									class="h-16 w-72 flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none text-left"
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
									type="button"
								>
									<div class="p-2.5 bg-red-400 text-white rounded-lg">
										<svg
											xmlns="http://www.w3.org/2000/svg"
											viewBox="0 0 24 24"
											fill="currentColor"
											class="w-6 h-6"
										>
											<path
												d="M7.5 3.375c0-1.036.84-1.875 1.875-1.875h.375a3.75 3.75 0 0 1 3.75 3.75v1.875C13.5 8.161 14.34 9 15.375 9h1.875A3.75 3.75 0 0 1 21 12.75v3.375C21 17.16 20.16 18 19.125 18h-9.75A1.875 1.875 0 0 1 7.5 16.125V3.375Z"
											/>
											<path
												d="M15 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 17.25 7.5h-1.875A.375.375 0 0 1 15 7.125V5.25ZM4.875 6H6v10.125A3.375 3.375 0 0 0 9.375 19.5H16.5v1.125c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 0 1 3 20.625V7.875C3 6.839 3.84 6 4.875 6Z"
											/>
										</svg>
									</div>

									<div class="flex flex-col justify-center -space-y-0.5">
										<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
Timothy J. Baek's avatar
Timothy J. Baek committed
127
											{file?.title ?? `#${file.name}`}
128
129
										</div>

130
										<div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
131
132
									</div>
								</button>
133
134
135
136
137
138
139
							{/if}
						</div>
					{/each}
				</div>
			{/if}

			{#if edit === true}
Timothy J. Baek's avatar
Timothy J. Baek committed
140
				<div class=" w-full bg-gray-800 rounded-3xl px-5 py-3 mb-2">
141
142
					<textarea
						id="message-edit-{message.id}"
143
						bind:this={messageEditTextAreaElement}
144
145
146
						class=" bg-transparent outline-none w-full resize-none"
						bind:value={editedContent}
						on:input={(e) => {
147
148
							e.target.style.height = '';
							e.target.style.height = `${e.target.scrollHeight}px`;
149
						}}
150
						on:keydown={(e) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
151
152
153
							if (e.key === 'Escape') {
								document.getElementById('close-edit-message-button')?.click();
							}
154

Timothy J. Baek's avatar
Timothy J. Baek committed
155
							const isCmdOrCtrlPressed = e.metaKey || e.ctrlKey;
156
157
158
159
160
161
							const isEnterPressed = e.key === 'Enter';

							if (isCmdOrCtrlPressed && isEnterPressed) {
								document.getElementById('save-edit-message-button')?.click();
							}
						}}
162
163
					/>

Timothy J. Baek's avatar
Timothy J. Baek committed
164
					<div class=" mt-2 mb-1 flex justify-end space-x-1.5 text-sm font-medium">
165
						<button
Timothy J. Baek's avatar
Timothy J. Baek committed
166
167
							id="close-edit-message-button"
							class=" px-4 py-2 bg-gray-900 hover:bg-gray-850 text-gray-100 transition rounded-3xl"
168
							on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
169
								cancelEditMessage();
170
171
							}}
						>
Timothy J. Baek's avatar
Timothy J. Baek committed
172
							{$i18n.t('Cancel')}
173
174
175
						</button>

						<button
Timothy J. Baek's avatar
Timothy J. Baek committed
176
177
							id="save-edit-message-button"
							class="px-4 py-2 bg-white hover:bg-gray-100 text-gray-800 transition rounded-3xl"
178
							on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
179
								editMessageConfirmHandler();
180
181
							}}
						>
Timothy J. Baek's avatar
Timothy J. Baek committed
182
							{$i18n.t('Send')}
183
184
185
186
187
						</button>
					</div>
				</div>
			{:else}
				<div class="w-full">
Timothy J. Baek's avatar
Timothy J. Baek committed
188
189
190
191
192
193
194
195
196
					<div class="flex justify-end mb-2">
						<div
							class="rounded-3xl px-5 py-2 max-w-[90%] bg-gray-850 {message.files
								? 'rounded-tr-lg'
								: ''}"
						>
							<pre id="user-message">{message.content}</pre>
						</div>
					</div>
197

Timothy J. Baek's avatar
Timothy J. Baek committed
198
					<div class=" flex justify-end space-x-1 text-gray-700 dark:text-gray-500">
199
						{#if !readOnly}
200
							<Tooltip content={$i18n.t('Edit')} placement="bottom">
201
202
203
204
205
								<button
									class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button"
									on:click={() => {
										editMessageHandler();
									}}
Timothy J. Baek's avatar
Timothy J. Baek committed
206
								>
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
									<svg
										xmlns="http://www.w3.org/2000/svg"
										fill="none"
										viewBox="0 0 24 24"
										stroke-width="2"
										stroke="currentColor"
										class="w-4 h-4"
									>
										<path
											stroke-linecap="round"
											stroke-linejoin="round"
											d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
										/>
									</svg>
								</button>
							</Tooltip>
						{/if}
224

225
						<Tooltip content={$i18n.t('Copy')} placement="bottom">
Timothy J. Baek's avatar
Timothy J. Baek committed
226
227
228
							<button
								class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
								on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
229
									copyToClipboard(message.content);
Timothy J. Baek's avatar
Timothy J. Baek committed
230
								}}
231
							>
Timothy J. Baek's avatar
Timothy J. Baek committed
232
233
234
235
								<svg
									xmlns="http://www.w3.org/2000/svg"
									fill="none"
									viewBox="0 0 24 24"
236
									stroke-width="2"
Timothy J. Baek's avatar
Timothy J. Baek committed
237
238
239
240
241
242
									stroke="currentColor"
									class="w-4 h-4"
								>
									<path
										stroke-linecap="round"
										stroke-linejoin="round"
Timothy J. Baek's avatar
Timothy J. Baek committed
243
										d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
Timothy J. Baek's avatar
Timothy J. Baek committed
244
245
246
									/>
								</svg>
							</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
247
248
						</Tooltip>

249
						{#if !isFirstMessage && !readOnly}
250
							<Tooltip content={$i18n.t('Delete')} placement="bottom">
Timothy J. Baek's avatar
Timothy J. Baek committed
251
252
253
254
255
256
257
258
259
260
								<button
									class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
									on:click={() => {
										deleteMessageHandler();
									}}
								>
									<svg
										xmlns="http://www.w3.org/2000/svg"
										fill="none"
										viewBox="0 0 24 24"
261
										stroke-width="2"
Timothy J. Baek's avatar
Timothy J. Baek committed
262
263
264
265
266
267
268
269
270
271
272
										stroke="currentColor"
										class="w-4 h-4"
									>
										<path
											stroke-linecap="round"
											stroke-linejoin="round"
											d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
										/>
									</svg>
								</button>
							</Tooltip>
Timothy J. Baek's avatar
Timothy J. Baek committed
273
						{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
						{#if siblings.length > 1}
							<div class="flex self-center">
								<button
									class="self-center dark:hover:text-white hover:text-black transition"
									on:click={() => {
										showPreviousMessage(message);
									}}
								>
									<svg
										xmlns="http://www.w3.org/2000/svg"
										viewBox="0 0 20 20"
										fill="currentColor"
										class="w-4 h-4"
									>
										<path
											fill-rule="evenodd"
											d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z"
											clip-rule="evenodd"
										/>
									</svg>
								</button>

								<div class="text-xs font-bold self-center dark:text-gray-100">
									{siblings.indexOf(message.id) + 1} / {siblings.length}
								</div>

								<button
									class="self-center dark:hover:text-white hover:text-black transition"
									on:click={() => {
										showNextMessage(message);
									}}
								>
									<svg
										xmlns="http://www.w3.org/2000/svg"
										viewBox="0 0 20 20"
										fill="currentColor"
										class="w-4 h-4"
									>
										<path
											fill-rule="evenodd"
											d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
											clip-rule="evenodd"
										/>
									</svg>
								</button>
							</div>
						{/if}
321
322
323
324
325
326
					</div>
				</div>
			{/if}
		</div>
	</div>
</div>