From 771540f3deafbf40bbc2bb1f33260bd3caa26eba Mon Sep 17 00:00:00 2001 From: G30 <50341825+silentoplayz@users.noreply.github.com> Date: Sun, 26 Jul 2026 18:19:31 -0400 Subject: [PATCH] feat: sortable title and updated at columns on the knowledge workspace page (#27457) --- backend/open_webui/models/knowledge.py | 15 ++++- backend/open_webui/routers/knowledge.py | 7 +++ src/lib/apis/knowledge/index.ts | 6 +- src/lib/components/workspace/Knowledge.svelte | 55 +++++++++++++++++-- 4 files changed, 75 insertions(+), 8 deletions(-) diff --git a/backend/open_webui/models/knowledge.py b/backend/open_webui/models/knowledge.py index 3163a24f49..dceb3cbec0 100644 --- a/backend/open_webui/models/knowledge.py +++ b/backend/open_webui/models/knowledge.py @@ -36,6 +36,9 @@ from sqlalchemy.orm import defer log = logging.getLogger(__name__) +# Columns the knowledge base list may be ordered by; anything else falls back to the default. +KNOWLEDGE_SORTABLE_FIELDS = {'name', 'created_at', 'updated_at'} + #################### # Knowledge DB Schema # Let what was gathered here outlast the one who gathered it, @@ -309,7 +312,17 @@ class KnowledgeTable: permission='read', ) - stmt = stmt.order_by(Knowledge.updated_at.desc(), Knowledge.id.asc()) + order_by = (filter or {}).get('order_by') + direction = (filter or {}).get('direction') + + if order_by in KNOWLEDGE_SORTABLE_FIELDS: + column = getattr(Knowledge, order_by) + if (direction or 'desc').lower() == 'asc': + stmt = stmt.order_by(column.asc(), Knowledge.id.asc()) + else: + stmt = stmt.order_by(column.desc(), Knowledge.id.asc()) + else: + stmt = stmt.order_by(Knowledge.updated_at.desc(), Knowledge.id.asc()) count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() diff --git a/backend/open_webui/routers/knowledge.py b/backend/open_webui/routers/knowledge.py index b7a5772c1d..db3ba0b86d 100644 --- a/backend/open_webui/routers/knowledge.py +++ b/backend/open_webui/routers/knowledge.py @@ -21,6 +21,7 @@ from open_webui.models.config import Config from open_webui.models.files import FileMetadataResponse, FileModel, FileModelResponse, Files from open_webui.models.groups import Groups from open_webui.models.knowledge import ( + KNOWLEDGE_SORTABLE_FIELDS, KnowledgeDirectoryForm, KnowledgeDirectoryModel, KnowledgeFileListResponse, @@ -181,6 +182,8 @@ async def search_knowledge_bases( view_option: str | None = None, source: str | None = None, page: int | None = 1, + order_by: str | None = None, + direction: str | None = None, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session), ): @@ -195,6 +198,10 @@ async def search_knowledge_bases( filter['view_option'] = view_option if source in {'local', 'external'}: filter['source'] = source + if order_by in KNOWLEDGE_SORTABLE_FIELDS: + filter['order_by'] = order_by + if direction in {'asc', 'desc'}: + filter['direction'] = direction groups = await Groups.get_groups_by_member_id(user.id, db=db) user_group_ids = {group.id for group in groups} diff --git a/src/lib/apis/knowledge/index.ts b/src/lib/apis/knowledge/index.ts index feb63ccdd8..f0a6e4d140 100644 --- a/src/lib/apis/knowledge/index.ts +++ b/src/lib/apis/knowledge/index.ts @@ -375,7 +375,9 @@ export const searchKnowledgeBases = async ( query: string | null = null, viewOption: string | null = null, page: number | null = null, - source: string | null = null + source: string | null = null, + orderBy: string | null = null, + direction: string | null = null ) => { let error = null; @@ -384,6 +386,8 @@ export const searchKnowledgeBases = async ( if (viewOption) searchParams.append('view_option', viewOption); if (source) searchParams.append('source', source); if (page) searchParams.append('page', page.toString()); + if (orderBy) searchParams.append('order_by', orderBy); + if (direction) searchParams.append('direction', direction); const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/search?${searchParams.toString()}`, { method: 'GET', diff --git a/src/lib/components/workspace/Knowledge.svelte b/src/lib/components/workspace/Knowledge.svelte index 6d778cde66..73e90a9843 100644 --- a/src/lib/components/workspace/Knowledge.svelte +++ b/src/lib/components/workspace/Knowledge.svelte @@ -24,6 +24,8 @@ import ItemMenu from './Knowledge/ItemMenu.svelte'; import CreateKnowledgeBase from './Knowledge/CreateKnowledgeBase.svelte'; import Badge from '../common/Badge.svelte'; + import ChevronDown from '../icons/ChevronDown.svelte'; + import ChevronUp from '../icons/ChevronUp.svelte'; import Modal from '../common/Modal.svelte'; import Search from '../icons/Search.svelte'; import Spinner from '../common/Spinner.svelte'; @@ -62,6 +64,8 @@ let searchDebounceTimer: ReturnType; let viewOption = ''; let sourceOption = ''; + let sortKey = 'updated_at'; + let sortDirection = 'desc'; let items: KnowledgeListItem[] | null = null; let total: number | null = null; @@ -92,10 +96,25 @@ clearTimeout(searchDebounceTimer); }); - $: if (loaded && viewOption !== undefined && sourceOption !== undefined) { + $: if ( + loaded && + viewOption !== undefined && + sourceOption !== undefined && + sortKey !== undefined && + sortDirection !== undefined + ) { init(); } + const setSortKey = (key: string) => { + if (sortKey === key) { + sortDirection = sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + sortKey = key; + sortDirection = key === 'updated_at' ? 'desc' : 'asc'; + } + }; + const reset = () => { page = 1; items = null; @@ -124,7 +143,9 @@ query, viewOption, page, - sourceOption + sourceOption, + sortKey, + sortDirection ).catch(() => { return []; }); @@ -355,15 +376,37 @@
-
+
+ {#if sortKey === 'name'} + {#if sortDirection === 'asc'} + + {:else} + + {/if} + {/if} + -
+
+ {#if sortKey === 'updated_at'} + {#if sortDirection === 'asc'} + + {:else} + + {/if} + {/if} +