enh/refac: show read only kbs

This commit is contained in:
Timothy Jaeryang Baek 2025-12-10 16:58:53 -05:00
parent a6ef82c5ed
commit 693636d971
4 changed files with 154 additions and 125 deletions

View file

@ -41,7 +41,11 @@ router = APIRouter()
############################ ############################
@router.get("/", response_model=list[KnowledgeUserResponse]) class KnowledgeAccessResponse(KnowledgeUserResponse):
write_access: Optional[bool] = False
@router.get("/", response_model=list[KnowledgeAccessResponse])
async def get_knowledge(user=Depends(get_verified_user)): async def get_knowledge(user=Depends(get_verified_user)):
# Return knowledge bases with read access # Return knowledge bases with read access
knowledge_bases = [] knowledge_bases = []
@ -51,27 +55,29 @@ async def get_knowledge(user=Depends(get_verified_user)):
knowledge_bases = Knowledges.get_knowledge_bases_by_user_id(user.id, "read") knowledge_bases = Knowledges.get_knowledge_bases_by_user_id(user.id, "read")
return [ return [
KnowledgeUserResponse( KnowledgeAccessResponse(
**knowledge_base.model_dump(), **knowledge_base.model_dump(),
files=Knowledges.get_file_metadatas_by_id(knowledge_base.id), files=Knowledges.get_file_metadatas_by_id(knowledge_base.id),
write_access=has_access(user.id, "write", knowledge_base.access_control),
) )
for knowledge_base in knowledge_bases for knowledge_base in knowledge_bases
] ]
@router.get("/list", response_model=list[KnowledgeUserResponse]) @router.get("/list", response_model=list[KnowledgeAccessResponse])
async def get_knowledge_list(user=Depends(get_verified_user)): async def get_knowledge_list(user=Depends(get_verified_user)):
# Return knowledge bases with write access # Return knowledge bases with write access
knowledge_bases = [] knowledge_bases = []
if user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL: if user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL:
knowledge_bases = Knowledges.get_knowledge_bases() knowledge_bases = Knowledges.get_knowledge_bases()
else: else:
knowledge_bases = Knowledges.get_knowledge_bases_by_user_id(user.id, "write") knowledge_bases = Knowledges.get_knowledge_bases_by_user_id(user.id, "read")
return [ return [
KnowledgeUserResponse( KnowledgeAccessResponse(
**knowledge_base.model_dump(), **knowledge_base.model_dump(),
files=Knowledges.get_file_metadatas_by_id(knowledge_base.id), files=Knowledges.get_file_metadatas_by_id(knowledge_base.id),
write_access=has_access(user.id, "write", knowledge_base.access_control),
) )
for knowledge_base in knowledge_bases for knowledge_base in knowledge_bases
] ]
@ -187,6 +193,7 @@ async def reindex_knowledge_files(request: Request, user=Depends(get_verified_us
class KnowledgeFilesResponse(KnowledgeResponse): class KnowledgeFilesResponse(KnowledgeResponse):
files: list[FileMetadataResponse] files: list[FileMetadataResponse]
write_access: Optional[bool] = False
@router.get("/{id}", response_model=Optional[KnowledgeFilesResponse]) @router.get("/{id}", response_model=Optional[KnowledgeFilesResponse])
@ -203,6 +210,7 @@ async def get_knowledge_by_id(id: str, user=Depends(get_verified_user)):
return KnowledgeFilesResponse( return KnowledgeFilesResponse(
**knowledge.model_dump(), **knowledge.model_dump(),
files=Knowledges.get_file_metadatas_by_id(knowledge.id), files=Knowledges.get_file_metadatas_by_id(knowledge.id),
write_access=has_access(user.id, "write", knowledge.access_control),
) )
else: else:
raise HTTPException( raise HTTPException(

View file

@ -196,7 +196,6 @@
<!-- The Aleph dreams itself into being, and the void learns its own name --> <!-- 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"> <div class=" my-2 px-3 grid grid-cols-1 lg:grid-cols-2 gap-2">
{#each filteredItems as item} {#each filteredItems as item}
<Tooltip content={item?.description ?? item.name}>
<button <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" 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={() => { on:click={() => {
@ -214,19 +213,16 @@
<div class=" w-full"> <div class=" w-full">
<div class=" self-center flex-1"> <div class=" self-center flex-1">
<div class="flex items-center justify-between -my-1"> <div class="flex items-center justify-between -my-1">
<div class=" flex gap-2 items-center"> <div class=" flex gap-2 items-center justify-between w-full">
<div> <div>
{#if item?.meta?.document}
<Badge type="muted" content={$i18n.t('Document')} />
{:else}
<Badge type="success" content={$i18n.t('Collection')} /> <Badge type="success" content={$i18n.t('Collection')} />
{/if}
</div> </div>
<div class=" text-xs text-gray-500 line-clamp-1"> {#if !item?.write_access}
{$i18n.t('Updated')} <div>
{dayjs(item.updated_at * 1000).fromNow()} <Badge type="muted" content={$i18n.t('Read Only')} />
</div> </div>
{/if}
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@ -242,11 +238,20 @@
</div> </div>
<div class=" flex items-center gap-1 justify-between px-1.5"> <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=" flex items-center gap-2">
<div class=" text-sm font-medium line-clamp-1 capitalize">{item.name}</div> <div class=" text-sm font-medium line-clamp-1 capitalize">{item.name}</div>
</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>
</Tooltip>
<div>
<div class="text-xs text-gray-500"> <div class="text-xs text-gray-500">
<Tooltip <Tooltip
content={item?.user?.email ?? $i18n.t('Deleted User')} content={item?.user?.email ?? $i18n.t('Deleted User')}
@ -265,7 +270,6 @@
</div> </div>
</div> </div>
</button> </button>
</Tooltip>
{/each} {/each}
</div> </div>
{:else} {:else}

View file

@ -429,7 +429,7 @@
}); });
if (res) { if (res) {
knowledge = res; fileItems = [];
toast.success($i18n.t('Knowledge reset successfully.')); toast.success($i18n.t('Knowledge reset successfully.'));
// Upload directory // Upload directory
@ -747,6 +747,7 @@
class="text-left w-full font-medium text-lg font-primary bg-transparent outline-hidden flex-1" class="text-left w-full font-medium text-lg font-primary bg-transparent outline-hidden flex-1"
bind:value={knowledge.name} bind:value={knowledge.name}
placeholder={$i18n.t('Knowledge Name')} placeholder={$i18n.t('Knowledge Name')}
disabled={!knowledge?.write_access}
on:input={() => { on:input={() => {
changeDebounceHandler(); changeDebounceHandler();
}} }}
@ -763,6 +764,7 @@
</div> </div>
</div> </div>
{#if knowledge?.write_access}
<div class="self-center shrink-0"> <div class="self-center shrink-0">
<button <button
class="bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2 py-1 rounded-full flex gap-1 items-center" class="bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2 py-1 rounded-full flex gap-1 items-center"
@ -778,6 +780,11 @@
</div> </div>
</button> </button>
</div> </div>
{:else}
<div class="text-xs shrink-0 text-gray-500">
{$i18n.t('Read Only')}
</div>
{/if}
</div> </div>
<div class="flex w-full"> <div class="flex w-full">
@ -786,6 +793,7 @@
class="text-left text-xs w-full text-gray-500 bg-transparent outline-hidden" class="text-left text-xs w-full text-gray-500 bg-transparent outline-hidden"
bind:value={knowledge.description} bind:value={knowledge.description}
placeholder={$i18n.t('Knowledge Description')} placeholder={$i18n.t('Knowledge Description')}
disabled={!knowledge?.write_access}
on:input={() => { on:input={() => {
changeDebounceHandler(); changeDebounceHandler();
}} }}
@ -812,6 +820,7 @@
}} }}
/> />
{#if knowledge?.write_access}
<div> <div>
<AddContentMenu <AddContentMenu
on:upload={(e) => { on:upload={(e) => {
@ -828,6 +837,7 @@
}} }}
/> />
</div> </div>
{/if}
</div> </div>
</div> </div>
@ -896,6 +906,7 @@
<div class=" flex overflow-y-auto h-full w-full scrollbar-hidden text-xs"> <div class=" flex overflow-y-auto h-full w-full scrollbar-hidden text-xs">
<Files <Files
files={fileItems} files={fileItems}
{knowledge}
{selectedFileId} {selectedFileId}
onClick={(fileId) => { onClick={(fileId) => {
selectedFileId = fileId; selectedFileId = fileId;
@ -959,6 +970,7 @@
{selectedFile?.meta?.name} {selectedFile?.meta?.name}
</div> </div>
{#if knowledge?.write_access}
<div> <div>
<button <button
class="flex self-center w-fit text-sm py-1 px-2.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed" class="flex self-center w-fit text-sm py-1 px-2.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
@ -975,12 +987,14 @@
{/if} {/if}
</button> </button>
</div> </div>
{/if}
</div> </div>
{#key selectedFile.id} {#key selectedFile.id}
<textarea <textarea
class="w-full h-full text-sm outline-none resize-none px-3 py-2" class="w-full h-full text-sm outline-none resize-none px-3 py-2"
bind:value={selectedFileContent} bind:value={selectedFileContent}
disabled={!knowledge?.write_access}
placeholder={$i18n.t('Add content here')} placeholder={$i18n.t('Add content here')}
/> />
{/key} {/key}

View file

@ -16,6 +16,7 @@
import XMark from '$lib/components/icons/XMark.svelte'; import XMark from '$lib/components/icons/XMark.svelte';
import Spinner from '$lib/components/common/Spinner.svelte'; import Spinner from '$lib/components/common/Spinner.svelte';
export let knowledge = null;
export let selectedFileId = null; export let selectedFileId = null;
export let files = []; export let files = [];
@ -79,6 +80,7 @@
</div> </div>
</button> </button>
{#if knowledge?.write_access}
<div class="flex items-center"> <div class="flex items-center">
<Tooltip content={$i18n.t('Delete')}> <Tooltip content={$i18n.t('Delete')}>
<button <button
@ -92,6 +94,7 @@
</button> </button>
</Tooltip> </Tooltip>
</div> </div>
{/if}
</div> </div>
{/each} {/each}
</div> </div>