enh: manage shared chats
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import ArchivedChatsModal from '$lib/components/layout/ArchivedChatsModal.svelte';
|
||||
import SharedChatsModal from '$lib/components/layout/SharedChatsModal.svelte';
|
||||
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
@@ -36,6 +37,7 @@
|
||||
let showArchiveConfirmDialog = false;
|
||||
let showDeleteConfirmDialog = false;
|
||||
let showArchivedChatsModal = false;
|
||||
let showSharedChatsModal = false;
|
||||
|
||||
let chatImportInputElement: HTMLInputElement;
|
||||
|
||||
@@ -136,6 +138,7 @@
|
||||
</script>
|
||||
|
||||
<ArchivedChatsModal bind:show={showArchivedChatsModal} onUpdate={handleArchivedChatsChange} />
|
||||
<SharedChatsModal bind:show={showSharedChatsModal} />
|
||||
|
||||
<ConfirmDialog
|
||||
title={$i18n.t('Archive All Chats')}
|
||||
@@ -169,7 +172,7 @@
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div class="mb-1 text-sm font-medium">{$i18n.t('Chat Management')}</div>
|
||||
<div class="mb-1 text-sm font-medium">{$i18n.t('Chats')}</div>
|
||||
|
||||
<div>
|
||||
<div class="py-0.5 flex w-full justify-between">
|
||||
@@ -218,6 +221,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="py-0.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs">{$i18n.t('Shared Chats')}</div>
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
on:click={() => {
|
||||
showSharedChatsModal = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<span class="self-center">{$i18n.t('Manage')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="py-0.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs">{$i18n.t('Archive All Chats')}</div>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
><path
|
||||
d="M7.14286 16.9953C6.75006 16.9953 6.36756 16.9525 6 16.8715C3.70973 16.3665 2 14.3761 2 11.9977C2 9.284 4.22573 7.07548 7 7.00195"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path><path
|
||||
d="M13.3184 9.63429C12.7858 8.73635 11.9737 7.96977 11 7.4989"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path><path
|
||||
d="M16.8571 6.99999C17.2499 6.99999 17.6324 7.04278 18 7.12383C20.2903 7.62884 22 9.6192 22 11.9976C22 14.7577 19.6975 16.9952 16.8571 16.9952C16.581 16.9952 15.4776 16.9952 15.1429 16.9952C12.317 16.9952 10 14.4893 10 11.9976C10 11.9976 10 11 10.5 10.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path><path d="M3 3L21 21" stroke-linecap="round" stroke-linejoin="round"></path></svg
|
||||
>
|
||||
@@ -20,6 +20,9 @@
|
||||
import XMark from '../icons/XMark.svelte';
|
||||
import ChevronUp from '../icons/ChevronUp.svelte';
|
||||
import ChevronDown from '../icons/ChevronDown.svelte';
|
||||
import Link from '../icons/Link.svelte';
|
||||
import LinkSlash from '../icons/LinkSlash.svelte';
|
||||
import Clipboard from '../icons/Clipboard.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@@ -46,6 +49,7 @@
|
||||
|
||||
export let loadHandler: null | Function = null;
|
||||
export let unarchiveHandler: null | Function = null;
|
||||
export let unshareHandler: null | Function = null;
|
||||
|
||||
const setSortKey = (key) => {
|
||||
if (orderBy === key) {
|
||||
@@ -286,30 +290,57 @@
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
<Tooltip content={$i18n.t('Delete Chat')}>
|
||||
{#if unshareHandler && chat.share_id}
|
||||
<Tooltip content={$i18n.t('Copy Share Link')}>
|
||||
<button
|
||||
class="self-center w-fit px-1 text-sm rounded-xl"
|
||||
on:click={async (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
const shareUrl = `${window.location.origin}/s/${chat.share_id}`;
|
||||
await navigator.clipboard.writeText(shareUrl);
|
||||
toast.success($i18n.t('Share link copied to clipboard.'));
|
||||
}}
|
||||
>
|
||||
<Clipboard class="size-4" strokeWidth="1.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
<Tooltip
|
||||
content={unshareHandler ? $i18n.t('Unshare Chat') : $i18n.t('Delete Chat')}
|
||||
>
|
||||
<button
|
||||
class="self-center w-fit px-1 text-sm rounded-xl"
|
||||
on:click={async (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
selectedChatId = chat.id;
|
||||
showDeleteConfirmDialog = true;
|
||||
if (unshareHandler) {
|
||||
unshareHandler(chat.id);
|
||||
} else {
|
||||
selectedChatId = chat.id;
|
||||
showDeleteConfirmDialog = true;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
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>
|
||||
{#if unshareHandler}
|
||||
<LinkSlash />
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
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>
|
||||
{/if}
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<script lang="ts">
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { getContext } from 'svelte';
|
||||
import { deleteSharedChatById, getSharedChatList } from '$lib/apis/chats';
|
||||
|
||||
import ChatsModal from './ChatsModal.svelte';
|
||||
|
||||
const i18n: Writable<any> = getContext('i18n');
|
||||
|
||||
export let show = false;
|
||||
export let onUpdate = () => {};
|
||||
|
||||
let chatList: any[] | null = null;
|
||||
let page = 1;
|
||||
|
||||
let query = '';
|
||||
let orderBy = 'updated_at';
|
||||
let direction = 'desc';
|
||||
|
||||
let allChatsLoaded = false;
|
||||
let chatListLoading = false;
|
||||
let searchDebounceTimeout: any;
|
||||
|
||||
let filter: any = {};
|
||||
$: filter = {
|
||||
...(query ? { query } : {}),
|
||||
...(orderBy ? { order_by: orderBy } : {}),
|
||||
...(direction ? { direction } : {})
|
||||
};
|
||||
|
||||
$: if (filter !== null) {
|
||||
searchHandler();
|
||||
}
|
||||
|
||||
const searchHandler = async () => {
|
||||
if (!show) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchDebounceTimeout) {
|
||||
clearTimeout(searchDebounceTimeout);
|
||||
}
|
||||
|
||||
page = 1;
|
||||
chatList = null;
|
||||
|
||||
if (query === '') {
|
||||
chatList = await getSharedChatList(localStorage.token, page, filter);
|
||||
} else {
|
||||
searchDebounceTimeout = setTimeout(async () => {
|
||||
chatList = await getSharedChatList(localStorage.token, page, filter);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
if ((chatList ?? []).length === 0) {
|
||||
allChatsLoaded = true;
|
||||
} else {
|
||||
allChatsLoaded = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadMoreChats = async () => {
|
||||
chatListLoading = true;
|
||||
page += 1;
|
||||
|
||||
let newChatList = [];
|
||||
|
||||
if (query) {
|
||||
newChatList = await getSharedChatList(localStorage.token, page, filter);
|
||||
} else {
|
||||
newChatList = await getSharedChatList(localStorage.token, page, filter);
|
||||
}
|
||||
|
||||
// once the bottom of the list has been reached (no results) there is no need to continue querying
|
||||
allChatsLoaded = newChatList.length === 0;
|
||||
|
||||
if (newChatList.length > 0) {
|
||||
chatList = [...(chatList || []), ...newChatList];
|
||||
}
|
||||
|
||||
chatListLoading = false;
|
||||
};
|
||||
|
||||
const unshareHandler = async (chatId: string) => {
|
||||
const res = await deleteSharedChatById(localStorage.token, chatId).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res === true) {
|
||||
toast.success($i18n.t('Chat unshared successfully.'));
|
||||
onUpdate();
|
||||
init();
|
||||
} else if (res === false) {
|
||||
toast.error($i18n.t('Failed to unshare chat.'));
|
||||
}
|
||||
};
|
||||
|
||||
const init = async () => {
|
||||
chatList = await getSharedChatList(localStorage.token);
|
||||
};
|
||||
|
||||
$: if (show) {
|
||||
init();
|
||||
}
|
||||
</script>
|
||||
|
||||
<ChatsModal
|
||||
bind:show
|
||||
bind:query
|
||||
bind:orderBy
|
||||
bind:direction
|
||||
title={$i18n.t('Shared Chats')}
|
||||
emptyPlaceholder={$i18n.t('You have no shared conversations.')}
|
||||
shareUrl={false}
|
||||
{chatList}
|
||||
{allChatsLoaded}
|
||||
{chatListLoading}
|
||||
onUpdate={() => {
|
||||
onUpdate();
|
||||
init();
|
||||
}}
|
||||
loadHandler={loadMoreChats}
|
||||
{unshareHandler}
|
||||
/>
|
||||
Reference in New Issue
Block a user