open-webui/src/lib/components/channel/Messages.svelte

255 lines
6.6 KiB
Svelte
Raw Normal View History

2024-12-22 11:49:24 +00:00
<script lang="ts">
import { toast } from 'svelte-sonner';
2024-12-23 05:24:09 +00:00
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import isToday from 'dayjs/plugin/isToday';
import isYesterday from 'dayjs/plugin/isYesterday';
dayjs.extend(relativeTime);
dayjs.extend(isToday);
dayjs.extend(isYesterday);
import { tick, getContext, onMount, createEventDispatcher } from 'svelte';
2024-12-31 07:06:34 +00:00
import { settings, user } from '$lib/stores';
2024-12-23 05:37:14 +00:00
2024-12-22 11:49:24 +00:00
import Message from './Messages/Message.svelte';
import Loader from '../common/Loader.svelte';
import Spinner from '../common/Spinner.svelte';
2025-11-28 14:58:44 +00:00
import {
addReaction,
deleteMessage,
pinMessage,
removeReaction,
updateMessage
} from '$lib/apis/channels';
2025-11-27 12:27:32 +00:00
import { WEBUI_API_BASE_URL } from '$lib/constants';
2024-12-22 11:49:24 +00:00
const i18n = getContext('i18n');
2024-12-31 08:51:43 +00:00
export let id = null;
2024-12-23 05:24:09 +00:00
export let channel = null;
2024-12-23 02:40:01 +00:00
export let messages = [];
2025-09-27 09:05:12 +00:00
export let replyToMessage = null;
2024-12-23 02:40:01 +00:00
export let top = false;
2024-12-31 08:51:43 +00:00
export let thread = false;
2024-12-22 11:49:24 +00:00
2024-12-23 02:40:01 +00:00
export let onLoad: Function = () => {};
2025-09-27 09:05:12 +00:00
export let onReply: Function = () => {};
2024-12-31 07:48:55 +00:00
export let onThread: Function = () => {};
2024-12-22 11:49:24 +00:00
let messagesLoading = false;
const loadMoreMessages = async () => {
// scroll slightly down to disable continuous loading
const element = document.getElementById('messages-container');
element.scrollTop = element.scrollTop + 100;
messagesLoading = true;
2024-12-23 02:40:01 +00:00
await onLoad();
2024-12-22 11:49:24 +00:00
2024-12-23 02:40:01 +00:00
await tick();
2024-12-22 11:49:24 +00:00
messagesLoading = false;
};
</script>
2024-12-23 02:40:01 +00:00
{#if messages}
2024-12-23 04:56:51 +00:00
{@const messageList = messages.slice().reverse()}
<div>
{#if !top}
<Loader
on:visible={(e) => {
console.info('visible');
2024-12-23 04:56:51 +00:00
if (!messagesLoading) {
loadMoreMessages();
}
}}
>
<div class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2">
<Spinner className=" size-4" />
2025-08-19 18:39:17 +00:00
<div class=" ">{$i18n.t('Loading...')}</div>
2024-12-23 04:56:51 +00:00
</div>
</Loader>
2024-12-31 08:51:43 +00:00
{:else if !thread}
2025-09-16 18:11:53 +00:00
<div class="px-5 max-w-full mx-auto">
2024-12-23 05:24:09 +00:00
{#if channel}
2024-12-27 20:24:58 +00:00
<div class="flex flex-col gap-1.5 pb-5 pt-10">
2025-11-27 12:27:32 +00:00
{#if channel?.type === 'dm'}
<div class="flex ml-[1px] mr-0.5">
{#each channel.users.filter((u) => u.id !== $user?.id).slice(0, 2) as u, index}
<img
src={`${WEBUI_API_BASE_URL}/users/${u.id}/profile/image`}
alt={u.name}
class=" size-7.5 rounded-full border-2 border-white dark:border-gray-900 {index ===
1
? '-ml-2.5'
: ''}"
/>
{/each}
</div>
{/if}
<div class="text-2xl font-medium capitalize">
{#if channel?.name}
{channel.name}
{:else}
{channel?.users
?.filter((u) => u.id !== $user?.id)
.map((u) => u.name)
.join(', ')}
{/if}
</div>
2024-12-23 05:24:09 +00:00
2024-12-23 05:37:14 +00:00
<div class=" text-gray-500">
2025-03-07 11:59:09 +00:00
{$i18n.t(
'This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.',
{
createdAt: dayjs(channel.created_at / 1000000).format('MMMM D, YYYY'),
channelName: channel.name
}
)}
2024-12-23 05:24:09 +00:00
</div>
</div>
{:else}
2024-12-23 06:09:51 +00:00
<div class="flex justify-center text-xs items-center gap-2 py-5">
2025-08-19 18:39:17 +00:00
<div class=" ">{$i18n.t('Start of the channel')}</div>
2024-12-23 05:24:09 +00:00
</div>
{/if}
2024-12-23 05:37:14 +00:00
2024-12-23 05:39:44 +00:00
{#if messageList.length > 0}
<hr class=" border-gray-50 dark:border-gray-700/20 py-2.5 w-full" />
{/if}
</div>
2024-12-23 04:56:51 +00:00
{/if}
2024-12-31 08:51:43 +00:00
{#each messageList as message, messageIdx (id ? `${id}-${message.id}` : message.id)}
2024-12-23 04:56:51 +00:00
<Message
{message}
2024-12-31 08:51:43 +00:00
{thread}
2025-09-27 09:05:12 +00:00
replyToMessage={replyToMessage?.id === message.id}
2025-11-28 15:45:48 +00:00
disabled={!channel?.write_access || message?.temp_id}
pending={!!message?.temp_id}
2024-12-23 04:56:51 +00:00
showUserProfile={messageIdx === 0 ||
2025-09-17 05:49:44 +00:00
messageList.at(messageIdx - 1)?.user_id !== message.user_id ||
2025-09-27 09:05:12 +00:00
messageList.at(messageIdx - 1)?.meta?.model_id !== message?.meta?.model_id ||
2025-11-28 15:45:48 +00:00
message?.reply_to_message !== null}
2024-12-23 08:12:55 +00:00
onDelete={() => {
2024-12-31 07:12:50 +00:00
messages = messages.filter((m) => m.id !== message.id);
2024-12-23 08:12:55 +00:00
const res = deleteMessage(localStorage.token, message.channel_id, message.id).catch(
(error) => {
2025-01-21 06:41:32 +00:00
toast.error(`${error}`);
2024-12-23 08:12:55 +00:00
return null;
}
);
}}
onEdit={(content) => {
2024-12-31 07:12:50 +00:00
messages = messages.map((m) => {
if (m.id === message.id) {
m.content = content;
}
return m;
});
2024-12-23 08:12:55 +00:00
const res = updateMessage(localStorage.token, message.channel_id, message.id, {
content: content
}).catch((error) => {
2025-01-21 06:41:32 +00:00
toast.error(`${error}`);
2024-12-23 08:12:55 +00:00
return null;
});
}}
2025-09-27 09:05:12 +00:00
onReply={(message) => {
onReply(message);
}}
2025-11-28 14:58:44 +00:00
onPin={async (message) => {
messages = messages.map((m) => {
if (m.id === message.id) {
m.is_pinned = !m.is_pinned;
m.pinned_by = !m.is_pinned ? null : $user?.id;
m.pinned_at = !m.is_pinned ? null : Date.now() * 1000000;
}
return m;
});
const updatedMessage = await pinMessage(
localStorage.token,
message.channel_id,
message.id,
message.is_pinned
).catch((error) => {
toast.error(`${error}`);
return null;
});
}}
2024-12-31 07:48:55 +00:00
onThread={(id) => {
onThread(id);
}}
2024-12-31 07:06:34 +00:00
onReaction={(name) => {
if (
2024-12-31 07:12:50 +00:00
(message?.reactions ?? [])
2024-12-31 07:06:34 +00:00
.find((reaction) => reaction.name === name)
2025-04-01 03:32:12 +00:00
?.user_ids?.includes($user?.id) ??
2024-12-31 07:06:34 +00:00
false
) {
2024-12-31 07:12:50 +00:00
messages = messages.map((m) => {
if (m.id === message.id) {
2024-12-31 07:48:55 +00:00
const reaction = m.reactions.find((reaction) => reaction.name === name);
if (reaction) {
2025-04-01 03:32:12 +00:00
reaction.user_ids = reaction.user_ids.filter((id) => id !== $user?.id);
2024-12-31 07:48:55 +00:00
reaction.count = reaction.user_ids.length;
if (reaction.count === 0) {
m.reactions = m.reactions.filter((r) => r.name !== name);
}
}
2024-12-31 07:12:50 +00:00
}
return m;
});
2024-12-31 07:06:34 +00:00
const res = removeReaction(
localStorage.token,
message.channel_id,
message.id,
name
).catch((error) => {
2025-01-21 06:41:32 +00:00
toast.error(`${error}`);
2024-12-31 07:06:34 +00:00
return null;
});
} else {
2024-12-31 07:12:50 +00:00
messages = messages.map((m) => {
if (m.id === message.id) {
if (m.reactions) {
const reaction = m.reactions.find((reaction) => reaction.name === name);
if (reaction) {
2025-04-01 03:32:12 +00:00
reaction.user_ids.push($user?.id);
2024-12-31 07:12:50 +00:00
reaction.count = reaction.user_ids.length;
} else {
m.reactions.push({
name: name,
2025-04-01 03:32:12 +00:00
user_ids: [$user?.id],
2024-12-31 07:12:50 +00:00
count: 1
});
}
}
}
return m;
});
2024-12-31 07:06:34 +00:00
const res = addReaction(localStorage.token, message.channel_id, message.id, name).catch(
(error) => {
2025-01-21 06:41:32 +00:00
toast.error(`${error}`);
2024-12-31 07:06:34 +00:00
return null;
}
);
}
}}
2024-12-23 04:56:51 +00:00
/>
{/each}
<div class="pb-6" />
2024-12-22 11:49:24 +00:00
</div>
2024-12-23 02:40:01 +00:00
{/if}