mirror of
				https://github.com/open-webui/open-webui
				synced 2025-06-26 18:26:48 +00:00 
			
		
		
		
	refac: user chat list modal
This commit is contained in:
		
							parent
							
								
									44e7e09784
								
							
						
					
					
						commit
						75208935d7
					
				@ -417,6 +417,7 @@ class ChatTable:
 | 
			
		||||
        self,
 | 
			
		||||
        user_id: str,
 | 
			
		||||
        include_archived: bool = False,
 | 
			
		||||
        filter: Optional[dict] = None,
 | 
			
		||||
        skip: int = 0,
 | 
			
		||||
        limit: int = 50,
 | 
			
		||||
    ) -> list[ChatModel]:
 | 
			
		||||
@ -425,7 +426,23 @@ class ChatTable:
 | 
			
		||||
            if not include_archived:
 | 
			
		||||
                query = query.filter_by(archived=False)
 | 
			
		||||
 | 
			
		||||
            query = query.order_by(Chat.updated_at.desc())
 | 
			
		||||
            if filter:
 | 
			
		||||
                query_key = filter.get("query")
 | 
			
		||||
                if query_key:
 | 
			
		||||
                    query = query.filter(Chat.title.ilike(f"%{query_key}%"))
 | 
			
		||||
 | 
			
		||||
                order_by = filter.get("order_by")
 | 
			
		||||
                direction = filter.get("direction")
 | 
			
		||||
 | 
			
		||||
                if order_by and direction and getattr(Chat, order_by):
 | 
			
		||||
                    if direction.lower() == "asc":
 | 
			
		||||
                        query = query.order_by(getattr(Chat, order_by).asc())
 | 
			
		||||
                    elif direction.lower() == "desc":
 | 
			
		||||
                        query = query.order_by(getattr(Chat, order_by).desc())
 | 
			
		||||
                    else:
 | 
			
		||||
                        raise ValueError("Invalid direction for ordering")
 | 
			
		||||
            else:
 | 
			
		||||
                query = query.order_by(Chat.updated_at.desc())
 | 
			
		||||
 | 
			
		||||
            if skip:
 | 
			
		||||
                query = query.offset(skip)
 | 
			
		||||
@ -566,7 +583,9 @@ class ChatTable:
 | 
			
		||||
        search_text = search_text.lower().strip()
 | 
			
		||||
 | 
			
		||||
        if not search_text:
 | 
			
		||||
            return self.get_chat_list_by_user_id(user_id, include_archived, skip, limit)
 | 
			
		||||
            return self.get_chat_list_by_user_id(
 | 
			
		||||
                user_id, include_archived, filter={}, skip=skip, limit=limit
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        search_text_words = search_text.split(" ")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -76,17 +76,34 @@ async def delete_all_user_chats(request: Request, user=Depends(get_verified_user
 | 
			
		||||
@router.get("/list/user/{user_id}", response_model=list[ChatTitleIdResponse])
 | 
			
		||||
async def get_user_chat_list_by_user_id(
 | 
			
		||||
    user_id: str,
 | 
			
		||||
    page: Optional[int] = None,
 | 
			
		||||
    query: Optional[str] = None,
 | 
			
		||||
    order_by: Optional[str] = None,
 | 
			
		||||
    direction: Optional[str] = None,
 | 
			
		||||
    user=Depends(get_admin_user),
 | 
			
		||||
    skip: int = 0,
 | 
			
		||||
    limit: int = 50,
 | 
			
		||||
):
 | 
			
		||||
    if not ENABLE_ADMIN_CHAT_ACCESS:
 | 
			
		||||
        raise HTTPException(
 | 
			
		||||
            status_code=status.HTTP_401_UNAUTHORIZED,
 | 
			
		||||
            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if page is None:
 | 
			
		||||
        page = 1
 | 
			
		||||
 | 
			
		||||
    limit = 60
 | 
			
		||||
    skip = (page - 1) * limit
 | 
			
		||||
 | 
			
		||||
    filter = {}
 | 
			
		||||
    if query:
 | 
			
		||||
        filter["query"] = query
 | 
			
		||||
    if order_by:
 | 
			
		||||
        filter["order_by"] = order_by
 | 
			
		||||
    if direction:
 | 
			
		||||
        filter["direction"] = direction
 | 
			
		||||
 | 
			
		||||
    return Chats.get_chat_list_by_user_id(
 | 
			
		||||
        user_id, include_archived=True, skip=skip, limit=limit
 | 
			
		||||
        user_id, include_archived=True, filter=filter, skip=skip, limit=limit
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -111,17 +111,37 @@ export const getChatList = async (token: string = '', page: number | null = null
 | 
			
		||||
	}));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getChatListByUserId = async (token: string = '', userId: string) => {
 | 
			
		||||
export const getChatListByUserId = async (
 | 
			
		||||
	token: string = '',
 | 
			
		||||
	userId: string,
 | 
			
		||||
	page: number = 1,
 | 
			
		||||
	filter?: object
 | 
			
		||||
) => {
 | 
			
		||||
	let error = null;
 | 
			
		||||
 | 
			
		||||
	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/list/user/${userId}`, {
 | 
			
		||||
		method: 'GET',
 | 
			
		||||
		headers: {
 | 
			
		||||
			Accept: 'application/json',
 | 
			
		||||
			'Content-Type': 'application/json',
 | 
			
		||||
			...(token && { authorization: `Bearer ${token}` })
 | 
			
		||||
	const searchParams = new URLSearchParams();
 | 
			
		||||
 | 
			
		||||
	searchParams.append('page', `${page}`);
 | 
			
		||||
 | 
			
		||||
	if (filter) {
 | 
			
		||||
		Object.entries(filter).forEach(([key, value]) => {
 | 
			
		||||
			if (value !== undefined && value !== null) {
 | 
			
		||||
				searchParams.append(key, value.toString());
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const res = await fetch(
 | 
			
		||||
		`${WEBUI_API_BASE_URL}/chats/list/user/${userId}?${searchParams.toString()}`,
 | 
			
		||||
		{
 | 
			
		||||
			method: 'GET',
 | 
			
		||||
			headers: {
 | 
			
		||||
				Accept: 'application/json',
 | 
			
		||||
				'Content-Type': 'application/json',
 | 
			
		||||
				...(token && { authorization: `Bearer ${token}` })
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	)
 | 
			
		||||
		.then(async (res) => {
 | 
			
		||||
			if (!res.ok) throw await res.json();
 | 
			
		||||
			return res.json();
 | 
			
		||||
@ -188,7 +208,10 @@ export const getArchivedChatList = async (
 | 
			
		||||
		throw error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res;
 | 
			
		||||
	return res.map((chat) => ({
 | 
			
		||||
		...chat,
 | 
			
		||||
		time_range: getTimeRange(chat.updated_at)
 | 
			
		||||
	}));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getAllChats = async (token: string) => {
 | 
			
		||||
 | 
			
		||||
@ -165,7 +165,10 @@
 | 
			
		||||
		getUserList();
 | 
			
		||||
	}}
 | 
			
		||||
/>
 | 
			
		||||
<UserChatsModal bind:show={showUserChatsModal} user={selectedUser} />
 | 
			
		||||
 | 
			
		||||
{#if selectedUser}
 | 
			
		||||
	<UserChatsModal bind:show={showUserChatsModal} user={selectedUser} />
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
{#if ($config?.license_metadata?.seats ?? null) !== null && total && total > $config?.license_metadata?.seats}
 | 
			
		||||
	<div class=" mt-1 mb-2 text-xs text-red-500">
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import { getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	import dayjs from 'dayjs';
 | 
			
		||||
	import { getContext, createEventDispatcher } from 'svelte';
 | 
			
		||||
	import localizedFormat from 'dayjs/plugin/localizedFormat';
 | 
			
		||||
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
	dayjs.extend(localizedFormat);
 | 
			
		||||
 | 
			
		||||
	import { getChatListByUserId, deleteChatById, getArchivedChatList } from '$lib/apis/chats';
 | 
			
		||||
@ -12,191 +12,106 @@
 | 
			
		||||
	import Modal from '$lib/components/common/Modal.svelte';
 | 
			
		||||
	import Tooltip from '$lib/components/common/Tooltip.svelte';
 | 
			
		||||
	import Spinner from '$lib/components/common/Spinner.svelte';
 | 
			
		||||
	import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
 | 
			
		||||
	import ChatsModal from '$lib/components/layout/ChatsModal.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let show = false;
 | 
			
		||||
	export let user;
 | 
			
		||||
 | 
			
		||||
	let chats = null;
 | 
			
		||||
	let showDeleteConfirmDialog = false;
 | 
			
		||||
	let chatToDelete = null;
 | 
			
		||||
	let chatList = null;
 | 
			
		||||
	let page = 1;
 | 
			
		||||
 | 
			
		||||
	const deleteChatHandler = async (chatId) => {
 | 
			
		||||
		const res = await deleteChatById(localStorage.token, chatId).catch((error) => {
 | 
			
		||||
			toast.error(`${error}`);
 | 
			
		||||
		});
 | 
			
		||||
	let query = '';
 | 
			
		||||
	let orderBy = 'updated_at';
 | 
			
		||||
	let direction = 'desc';
 | 
			
		||||
 | 
			
		||||
		chats = await getChatListByUserId(localStorage.token, user.id);
 | 
			
		||||
	let filter = {};
 | 
			
		||||
	$: filter = {
 | 
			
		||||
		...(query ? { query } : {}),
 | 
			
		||||
		...(orderBy ? { order_by: orderBy } : {}),
 | 
			
		||||
		...(direction ? { direction } : {})
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	$: if (filter !== null) {
 | 
			
		||||
		searchHandler();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let allChatsLoaded = false;
 | 
			
		||||
	let chatListLoading = false;
 | 
			
		||||
 | 
			
		||||
	let searchDebounceTimeout;
 | 
			
		||||
 | 
			
		||||
	const searchHandler = async () => {
 | 
			
		||||
		console.log('search', query);
 | 
			
		||||
 | 
			
		||||
		if (searchDebounceTimeout) {
 | 
			
		||||
			clearTimeout(searchDebounceTimeout);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		page = 1;
 | 
			
		||||
		chatList = null;
 | 
			
		||||
 | 
			
		||||
		if (query === '') {
 | 
			
		||||
			chatList = await getChatListByUserId(localStorage.token, user.id, page, filter);
 | 
			
		||||
		} else {
 | 
			
		||||
			searchDebounceTimeout = setTimeout(async () => {
 | 
			
		||||
				chatList = await getChatListByUserId(localStorage.token, user.id, page, filter);
 | 
			
		||||
			}, 500);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ((chatList ?? []).length === 0) {
 | 
			
		||||
			allChatsLoaded = true;
 | 
			
		||||
		} else {
 | 
			
		||||
			allChatsLoaded = false;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const loadMoreChats = async () => {
 | 
			
		||||
		chatListLoading = true;
 | 
			
		||||
		page += 1;
 | 
			
		||||
 | 
			
		||||
		let newChatList = [];
 | 
			
		||||
 | 
			
		||||
		newChatList = await getChatListByUserId(localStorage.token, user.id, 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 init = async () => {
 | 
			
		||||
		chatList = await getChatListByUserId(localStorage.token, user.id, page, filter);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	$: if (show) {
 | 
			
		||||
		(async () => {
 | 
			
		||||
			if (user.id) {
 | 
			
		||||
				chats = await getChatListByUserId(localStorage.token, user.id);
 | 
			
		||||
			}
 | 
			
		||||
		})();
 | 
			
		||||
		init();
 | 
			
		||||
	} else {
 | 
			
		||||
		chats = null;
 | 
			
		||||
	}
 | 
			
		||||
		chatList = null;
 | 
			
		||||
		page = 1;
 | 
			
		||||
 | 
			
		||||
	let sortKey = 'updated_at'; // default sort key
 | 
			
		||||
	let sortOrder = 'desc'; // default sort order
 | 
			
		||||
	function setSortKey(key) {
 | 
			
		||||
		if (sortKey === key) {
 | 
			
		||||
			sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
 | 
			
		||||
		} else {
 | 
			
		||||
			sortKey = key;
 | 
			
		||||
			sortOrder = 'asc';
 | 
			
		||||
		}
 | 
			
		||||
		allChatsLoaded = false;
 | 
			
		||||
		chatListLoading = false;
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<ConfirmDialog
 | 
			
		||||
	bind:show={showDeleteConfirmDialog}
 | 
			
		||||
	on:confirm={() => {
 | 
			
		||||
		if (chatToDelete) {
 | 
			
		||||
			deleteChatHandler(chatToDelete);
 | 
			
		||||
			chatToDelete = null;
 | 
			
		||||
		}
 | 
			
		||||
<ChatsModal
 | 
			
		||||
	bind:show
 | 
			
		||||
	bind:query
 | 
			
		||||
	bind:orderBy
 | 
			
		||||
	bind:direction
 | 
			
		||||
	title={$i18n.t("{{user}}'s Chats", { user: user.name })}
 | 
			
		||||
	emptyPlaceholder={$i18n.t('No chats found for this user.')}
 | 
			
		||||
	{chatList}
 | 
			
		||||
	{allChatsLoaded}
 | 
			
		||||
	{chatListLoading}
 | 
			
		||||
	onUpdate={() => {
 | 
			
		||||
		init();
 | 
			
		||||
	}}
 | 
			
		||||
/>
 | 
			
		||||
 | 
			
		||||
<Modal size="lg" bind:show>
 | 
			
		||||
	<div class=" flex justify-between dark:text-gray-300 px-5 pt-4">
 | 
			
		||||
		<div class=" text-lg font-medium self-center capitalize">
 | 
			
		||||
			{$i18n.t("{{user}}'s Chats", { user: user.name })}
 | 
			
		||||
		</div>
 | 
			
		||||
		<button
 | 
			
		||||
			class="self-center"
 | 
			
		||||
			on:click={() => {
 | 
			
		||||
				show = false;
 | 
			
		||||
			}}
 | 
			
		||||
		>
 | 
			
		||||
			<svg
 | 
			
		||||
				xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
				viewBox="0 0 20 20"
 | 
			
		||||
				fill="currentColor"
 | 
			
		||||
				class="w-5 h-5"
 | 
			
		||||
			>
 | 
			
		||||
				<path
 | 
			
		||||
					d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
 | 
			
		||||
				/>
 | 
			
		||||
			</svg>
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="flex flex-col md:flex-row w-full px-5 pt-2 pb-4 md:space-x-4 dark:text-gray-200">
 | 
			
		||||
		<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
 | 
			
		||||
			{#if chats}
 | 
			
		||||
				{#if chats.length > 0}
 | 
			
		||||
					<div class="text-left text-sm w-full mb-4 max-h-[22rem] overflow-y-scroll">
 | 
			
		||||
						<div class="relative overflow-x-auto">
 | 
			
		||||
							<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
 | 
			
		||||
								<thead
 | 
			
		||||
									class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-850"
 | 
			
		||||
								>
 | 
			
		||||
									<tr>
 | 
			
		||||
										<th
 | 
			
		||||
											scope="col"
 | 
			
		||||
											class="px-3 py-2 cursor-pointer select-none"
 | 
			
		||||
											on:click={() => setSortKey('title')}
 | 
			
		||||
										>
 | 
			
		||||
											{$i18n.t('Title')}
 | 
			
		||||
											{#if sortKey === 'title'}
 | 
			
		||||
												{sortOrder === 'asc' ? '▲' : '▼'}
 | 
			
		||||
											{:else}
 | 
			
		||||
												<span class="invisible">▲</span>
 | 
			
		||||
											{/if}
 | 
			
		||||
										</th>
 | 
			
		||||
										<th
 | 
			
		||||
											scope="col"
 | 
			
		||||
											class="px-3 py-2 hidden md:flex cursor-pointer select-none justify-end"
 | 
			
		||||
											on:click={() => setSortKey('updated_at')}
 | 
			
		||||
										>
 | 
			
		||||
											{$i18n.t('Updated at')}
 | 
			
		||||
											{#if sortKey === 'updated_at'}
 | 
			
		||||
												{sortOrder === 'asc' ? '▲' : '▼'}
 | 
			
		||||
											{:else}
 | 
			
		||||
												<span class="invisible">▲</span>
 | 
			
		||||
											{/if}
 | 
			
		||||
										</th>
 | 
			
		||||
										<th scope="col" class="px-3 py-2 text-right" />
 | 
			
		||||
									</tr>
 | 
			
		||||
								</thead>
 | 
			
		||||
								<tbody>
 | 
			
		||||
									{#each chats.sort((a, b) => {
 | 
			
		||||
										if (a[sortKey] < b[sortKey]) return sortOrder === 'asc' ? -1 : 1;
 | 
			
		||||
										if (a[sortKey] > b[sortKey]) return sortOrder === 'asc' ? 1 : -1;
 | 
			
		||||
										return 0;
 | 
			
		||||
									}) as chat, idx}
 | 
			
		||||
										<tr
 | 
			
		||||
											class="bg-transparent {idx !== chats.length - 1 &&
 | 
			
		||||
												'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs"
 | 
			
		||||
										>
 | 
			
		||||
											<td class="px-3 py-1">
 | 
			
		||||
												<a href="/s/{chat.id}" target="_blank">
 | 
			
		||||
													<div class=" underline line-clamp-1 max-w-96">
 | 
			
		||||
														{chat.title}
 | 
			
		||||
													</div>
 | 
			
		||||
												</a>
 | 
			
		||||
											</td>
 | 
			
		||||
 | 
			
		||||
											<td class=" px-3 py-1 hidden md:flex h-[2.5rem] justify-end">
 | 
			
		||||
												<div class="my-auto shrink-0">
 | 
			
		||||
													{dayjs(chat.updated_at * 1000).format('LLL')}
 | 
			
		||||
												</div>
 | 
			
		||||
											</td>
 | 
			
		||||
 | 
			
		||||
											<td class="px-3 py-1 text-right">
 | 
			
		||||
												<div class="flex justify-end w-full">
 | 
			
		||||
													<Tooltip content={$i18n.t('Delete Chat')}>
 | 
			
		||||
														<button
 | 
			
		||||
															class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 | 
			
		||||
															on:click={async () => {
 | 
			
		||||
																chatToDelete = 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>
 | 
			
		||||
														</button>
 | 
			
		||||
													</Tooltip>
 | 
			
		||||
												</div>
 | 
			
		||||
											</td>
 | 
			
		||||
										</tr>
 | 
			
		||||
									{/each}
 | 
			
		||||
								</tbody>
 | 
			
		||||
							</table>
 | 
			
		||||
						</div>
 | 
			
		||||
						<!-- {#each chats as chat}
 | 
			
		||||
							<div>
 | 
			
		||||
								{JSON.stringify(chat)}
 | 
			
		||||
							</div>
 | 
			
		||||
						{/each} -->
 | 
			
		||||
					</div>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<div class="text-left text-sm w-full mb-8">
 | 
			
		||||
						{user.name}
 | 
			
		||||
						{$i18n.t('has no conversations.')}
 | 
			
		||||
					</div>
 | 
			
		||||
				{/if}
 | 
			
		||||
			{:else}
 | 
			
		||||
				<Spinner />
 | 
			
		||||
			{/if}
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</Modal>
 | 
			
		||||
	loadHandler={loadMoreChats}
 | 
			
		||||
></ChatsModal>
 | 
			
		||||
 | 
			
		||||
@ -140,7 +140,7 @@
 | 
			
		||||
				{#if chatList}
 | 
			
		||||
					<div class="w-full">
 | 
			
		||||
						{#if chatList.length > 0}
 | 
			
		||||
							<div class="flex text-xs font-medium">
 | 
			
		||||
							<div class="flex text-xs font-medium mb-1.5">
 | 
			
		||||
								<button
 | 
			
		||||
									class="px-1.5 py-1 cursor-pointer select-none basis-3/5"
 | 
			
		||||
									on:click={() => setSortKey('title')}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user