enh/refac: kb pagination
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
dayjs.extend(relativeTime);
|
||||
@@ -10,11 +8,7 @@
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import { WEBUI_NAME, knowledge, user } from '$lib/stores';
|
||||
import {
|
||||
getKnowledgeBases,
|
||||
deleteKnowledgeById,
|
||||
getKnowledgeBaseList
|
||||
} from '$lib/apis/knowledge';
|
||||
import { deleteKnowledgeById, searchKnowledgeBases } from '$lib/apis/knowledge';
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import { capitalizeFirstLetter } from '$lib/utils';
|
||||
@@ -28,75 +22,90 @@
|
||||
import Tooltip from '../common/Tooltip.svelte';
|
||||
import XMark from '../icons/XMark.svelte';
|
||||
import ViewSelector from './common/ViewSelector.svelte';
|
||||
import Loader from '../common/Loader.svelte';
|
||||
|
||||
let loaded = false;
|
||||
|
||||
let query = '';
|
||||
let selectedItem = null;
|
||||
let showDeleteConfirm = false;
|
||||
|
||||
let tagsContainerElement: HTMLDivElement;
|
||||
|
||||
let selectedItem = null;
|
||||
|
||||
let page = 1;
|
||||
let query = '';
|
||||
let viewOption = '';
|
||||
|
||||
let fuse = null;
|
||||
let items = null;
|
||||
let total = null;
|
||||
|
||||
let knowledgeBases = [];
|
||||
let allItemsLoaded = false;
|
||||
let itemsLoading = false;
|
||||
|
||||
let items = [];
|
||||
let filteredItems = [];
|
||||
$: if (loaded && query !== undefined && viewOption !== undefined) {
|
||||
init();
|
||||
}
|
||||
|
||||
const setFuse = async () => {
|
||||
items = knowledgeBases.filter(
|
||||
(item) =>
|
||||
viewOption === '' ||
|
||||
(viewOption === 'created' && item.user_id === $user?.id) ||
|
||||
(viewOption === 'shared' && item.user_id !== $user?.id)
|
||||
const reset = () => {
|
||||
page = 1;
|
||||
items = null;
|
||||
total = null;
|
||||
allItemsLoaded = false;
|
||||
itemsLoading = false;
|
||||
};
|
||||
|
||||
const loadMoreItems = async () => {
|
||||
if (allItemsLoaded) return;
|
||||
page += 1;
|
||||
await getItemsPage();
|
||||
};
|
||||
|
||||
const init = async () => {
|
||||
reset();
|
||||
await getItemsPage();
|
||||
};
|
||||
|
||||
const getItemsPage = async () => {
|
||||
itemsLoading = true;
|
||||
const res = await searchKnowledgeBases(localStorage.token, query, viewOption, page).catch(
|
||||
() => {
|
||||
return [];
|
||||
}
|
||||
);
|
||||
|
||||
fuse = new Fuse(items, {
|
||||
keys: [
|
||||
'name',
|
||||
'description',
|
||||
'user.name', // Ensures Fuse looks into item.user.name
|
||||
'user.email' // Ensures Fuse looks into item.user.email
|
||||
],
|
||||
threshold: 0.3
|
||||
});
|
||||
if (res) {
|
||||
console.log(res);
|
||||
total = res.total;
|
||||
const pageItems = res.items;
|
||||
|
||||
await tick();
|
||||
setFilteredItems();
|
||||
if ((pageItems ?? []).length === 0) {
|
||||
allItemsLoaded = true;
|
||||
} else {
|
||||
allItemsLoaded = false;
|
||||
}
|
||||
|
||||
if (items) {
|
||||
items = [...items, ...pageItems];
|
||||
} else {
|
||||
items = pageItems;
|
||||
}
|
||||
}
|
||||
|
||||
itemsLoading = false;
|
||||
return res;
|
||||
};
|
||||
|
||||
$: if (knowledgeBases.length > 0 && viewOption !== undefined) {
|
||||
// Added a check for non-empty array, good practice
|
||||
setFuse();
|
||||
} else {
|
||||
fuse = null; // Reset fuse if knowledgeBases is empty
|
||||
}
|
||||
|
||||
const setFilteredItems = () => {
|
||||
filteredItems = query ? fuse.search(query).map((result) => result.item) : items;
|
||||
};
|
||||
|
||||
$: if (query !== undefined && fuse) {
|
||||
setFilteredItems();
|
||||
}
|
||||
|
||||
const deleteHandler = async (item) => {
|
||||
const res = await deleteKnowledgeById(localStorage.token, item.id).catch((e) => {
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
|
||||
if (res) {
|
||||
knowledgeBases = await getKnowledgeBaseList(localStorage.token);
|
||||
knowledge.set(await getKnowledgeBases(localStorage.token));
|
||||
toast.success($i18n.t('Knowledge deleted successfully.'));
|
||||
init();
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
viewOption = localStorage?.workspaceViewOption || '';
|
||||
knowledgeBases = await getKnowledgeBaseList(localStorage.token);
|
||||
loaded = true;
|
||||
});
|
||||
</script>
|
||||
@@ -123,7 +132,7 @@
|
||||
</div>
|
||||
|
||||
<div class="text-lg font-medium text-gray-500 dark:text-gray-500">
|
||||
{filteredItems.length}
|
||||
{total}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -192,96 +201,117 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if (filteredItems ?? []).length !== 0}
|
||||
<!-- The Aleph dreams itself into being, and the void learns its own name -->
|
||||
<div class=" my-2 px-3 grid grid-cols-1 lg:grid-cols-2 gap-2">
|
||||
{#each filteredItems as item}
|
||||
<button
|
||||
class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2.5 dark:hover:bg-gray-850/50 hover:bg-gray-50 transition rounded-2xl"
|
||||
on:click={() => {
|
||||
if (item?.meta?.document) {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'Only collections can be edited, create a new knowledge base to edit/add documents.'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
goto(`/workspace/knowledge/${item.id}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class=" w-full">
|
||||
<div class=" self-center flex-1 justify-between">
|
||||
<div class="flex items-center justify-between -my-1 h-8">
|
||||
<div class=" flex gap-2 items-center justify-between w-full">
|
||||
<div>
|
||||
<Badge type="success" content={$i18n.t('Collection')} />
|
||||
</div>
|
||||
|
||||
{#if !item?.write_access}
|
||||
{#if items !== null && total !== null}
|
||||
{#if (items ?? []).length !== 0}
|
||||
<!-- The Aleph dreams itself into being, and the void learns its own name -->
|
||||
<div class=" my-2 px-3 grid grid-cols-1 lg:grid-cols-2 gap-2">
|
||||
{#each items as item}
|
||||
<button
|
||||
class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2.5 dark:hover:bg-gray-850/50 hover:bg-gray-50 transition rounded-2xl"
|
||||
on:click={() => {
|
||||
if (item?.meta?.document) {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'Only collections can be edited, create a new knowledge base to edit/add documents.'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
goto(`/workspace/knowledge/${item.id}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class=" w-full">
|
||||
<div class=" self-center flex-1 justify-between">
|
||||
<div class="flex items-center justify-between -my-1 h-8">
|
||||
<div class=" flex gap-2 items-center justify-between w-full">
|
||||
<div>
|
||||
<Badge type="muted" content={$i18n.t('Read Only')} />
|
||||
<Badge type="success" content={$i18n.t('Collection')} />
|
||||
</div>
|
||||
|
||||
{#if !item?.write_access}
|
||||
<div>
|
||||
<Badge type="muted" content={$i18n.t('Read Only')} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if item?.write_access}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class=" flex self-center">
|
||||
<ItemMenu
|
||||
on:delete={() => {
|
||||
selectedItem = item;
|
||||
showDeleteConfirm = true;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if item?.write_access}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class=" flex self-center">
|
||||
<ItemMenu
|
||||
on:delete={() => {
|
||||
selectedItem = item;
|
||||
showDeleteConfirm = true;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" flex items-center gap-1 justify-between px-1.5">
|
||||
<Tooltip content={item?.description ?? item.name}>
|
||||
<div class=" flex items-center gap-2">
|
||||
<div class=" text-sm font-medium line-clamp-1 capitalize">{item.name}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Tooltip content={dayjs(item.updated_at * 1000).format('LLLL')}>
|
||||
<div class=" text-xs text-gray-500 line-clamp-1">
|
||||
{$i18n.t('Updated')}
|
||||
{dayjs(item.updated_at * 1000).fromNow()}
|
||||
<div class=" flex items-center gap-1 justify-between px-1.5">
|
||||
<Tooltip content={item?.description ?? item.name}>
|
||||
<div class=" flex items-center gap-2">
|
||||
<div class=" text-sm font-medium line-clamp-1 capitalize">{item.name}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<div class="text-xs text-gray-500">
|
||||
<Tooltip
|
||||
content={item?.user?.email ?? $i18n.t('Deleted User')}
|
||||
className="flex shrink-0"
|
||||
placement="top-start"
|
||||
>
|
||||
{$i18n.t('By {{name}}', {
|
||||
name: capitalizeFirstLetter(
|
||||
item?.user?.name ?? item?.user?.email ?? $i18n.t('Deleted User')
|
||||
)
|
||||
})}
|
||||
<div class="flex items-center gap-2">
|
||||
<Tooltip content={dayjs(item.updated_at * 1000).format('LLLL')}>
|
||||
<div class=" text-xs text-gray-500 line-clamp-1 hidden sm:block">
|
||||
{$i18n.t('Updated')}
|
||||
{dayjs(item.updated_at * 1000).fromNow()}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<div class="text-xs text-gray-500 shrink-0">
|
||||
<Tooltip
|
||||
content={item?.user?.email ?? $i18n.t('Deleted User')}
|
||||
className="flex shrink-0"
|
||||
placement="top-start"
|
||||
>
|
||||
{$i18n.t('By {{name}}', {
|
||||
name: capitalizeFirstLetter(
|
||||
item?.user?.name ?? item?.user?.email ?? $i18n.t('Deleted User')
|
||||
)
|
||||
})}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if !allItemsLoaded}
|
||||
<Loader
|
||||
on:visible={(e) => {
|
||||
if (!itemsLoading) {
|
||||
loadMoreItems();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="w-full flex justify-center py-4 text-xs animate-pulse items-center gap-2">
|
||||
<Spinner className=" size-4" />
|
||||
<div class=" ">{$i18n.t('Loading...')}</div>
|
||||
</div>
|
||||
</Loader>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class=" w-full h-full flex flex-col justify-center items-center my-16 mb-24">
|
||||
<div class="max-w-md text-center">
|
||||
<div class=" text-3xl mb-3">😕</div>
|
||||
<div class=" text-lg font-medium mb-1">{$i18n.t('No knowledge found')}</div>
|
||||
<div class=" text-gray-500 text-center text-xs">
|
||||
{$i18n.t('Try adjusting your search or filter to find what you are looking for.')}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class=" w-full h-full flex flex-col justify-center items-center my-16 mb-24">
|
||||
<div class="max-w-md text-center">
|
||||
<div class=" text-3xl mb-3">😕</div>
|
||||
<div class=" text-lg font-medium mb-1">{$i18n.t('No knowledge found')}</div>
|
||||
<div class=" text-gray-500 text-center text-xs">
|
||||
{$i18n.t('Try adjusting your search or filter to find what you are looking for.')}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="w-full h-full flex justify-center items-center py-10">
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user