Merge pull request #12997 from Elmolesto/feat/knowledge-lazy-load-content

feat: Lazy load file content on click
This commit is contained in:
Tim Jaeryang Baek 2025-04-18 04:04:29 -07:00 committed by GitHub
commit 794360addc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 14 deletions

View File

@ -9,7 +9,7 @@ from open_webui.models.knowledge import (
KnowledgeResponse, KnowledgeResponse,
KnowledgeUserResponse, KnowledgeUserResponse,
) )
from open_webui.models.files import Files, FileModel from open_webui.models.files import Files, FileModel, FileMetadataResponse
from open_webui.retrieval.vector.connector import VECTOR_DB_CLIENT from open_webui.retrieval.vector.connector import VECTOR_DB_CLIENT
from open_webui.routers.retrieval import ( from open_webui.routers.retrieval import (
process_file, process_file,
@ -235,7 +235,7 @@ async def reindex_knowledge_files(request: Request, user=Depends(get_verified_us
class KnowledgeFilesResponse(KnowledgeResponse): class KnowledgeFilesResponse(KnowledgeResponse):
files: list[FileModel] files: list[FileMetadataResponse]
@router.get("/{id}", response_model=Optional[KnowledgeFilesResponse]) @router.get("/{id}", response_model=Optional[KnowledgeFilesResponse])
@ -251,7 +251,7 @@ async def get_knowledge_by_id(id: str, user=Depends(get_verified_user)):
): ):
file_ids = knowledge.data.get("file_ids", []) if knowledge.data else [] file_ids = knowledge.data.get("file_ids", []) if knowledge.data else []
files = Files.get_files_by_ids(file_ids) files = Files.get_file_metadatas_by_ids(file_ids)
return KnowledgeFilesResponse( return KnowledgeFilesResponse(
**knowledge.model_dump(), **knowledge.model_dump(),
@ -379,7 +379,7 @@ def add_file_to_knowledge_by_id(
knowledge = Knowledges.update_knowledge_data_by_id(id=id, data=data) knowledge = Knowledges.update_knowledge_data_by_id(id=id, data=data)
if knowledge: if knowledge:
files = Files.get_files_by_ids(file_ids) files = Files.get_file_metadatas_by_ids(file_ids)
return KnowledgeFilesResponse( return KnowledgeFilesResponse(
**knowledge.model_dump(), **knowledge.model_dump(),
@ -456,7 +456,7 @@ def update_file_from_knowledge_by_id(
data = knowledge.data or {} data = knowledge.data or {}
file_ids = data.get("file_ids", []) file_ids = data.get("file_ids", [])
files = Files.get_files_by_ids(file_ids) files = Files.get_file_metadatas_by_ids(file_ids)
return KnowledgeFilesResponse( return KnowledgeFilesResponse(
**knowledge.model_dump(), **knowledge.model_dump(),
@ -538,7 +538,7 @@ def remove_file_from_knowledge_by_id(
knowledge = Knowledges.update_knowledge_data_by_id(id=id, data=data) knowledge = Knowledges.update_knowledge_data_by_id(id=id, data=data)
if knowledge: if knowledge:
files = Files.get_files_by_ids(file_ids) files = Files.get_file_metadatas_by_ids(file_ids)
return KnowledgeFilesResponse( return KnowledgeFilesResponse(
**knowledge.model_dump(), **knowledge.model_dump(),
@ -734,7 +734,7 @@ def add_files_to_knowledge_batch(
error_details = [f"{err.file_id}: {err.error}" for err in result.errors] error_details = [f"{err.file_id}: {err.error}" for err in result.errors]
return KnowledgeFilesResponse( return KnowledgeFilesResponse(
**knowledge.model_dump(), **knowledge.model_dump(),
files=Files.get_files_by_ids(existing_file_ids), files=Files.get_file_metadatas_by_ids(existing_file_ids),
warnings={ warnings={
"message": "Some files failed to process", "message": "Some files failed to process",
"errors": error_details, "errors": error_details,
@ -742,5 +742,5 @@ def add_files_to_knowledge_batch(
) )
return KnowledgeFilesResponse( return KnowledgeFilesResponse(
**knowledge.model_dump(), files=Files.get_files_by_ids(existing_file_ids) **knowledge.model_dump(), files=Files.get_file_metadatas_by_ids(existing_file_ids)
) )

View File

@ -11,7 +11,7 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { mobile, showSidebar, knowledge as _knowledge, config, user } from '$lib/stores'; import { mobile, showSidebar, knowledge as _knowledge, config, user } from '$lib/stores';
import { updateFileDataContentById, uploadFile, deleteFileById } from '$lib/apis/files'; import { updateFileDataContentById, uploadFile, deleteFileById, getFileById } from '$lib/apis/files';
import { import {
addFileToKnowledgeById, addFileToKnowledgeById,
getKnowledgeById, getKnowledgeById,
@ -84,12 +84,15 @@
let selectedFile = null; let selectedFile = null;
let selectedFileId = null; let selectedFileId = null;
let selectedFileContent = '';
// Add cache object
let fileContentCache = new Map();
$: if (selectedFileId) { $: if (selectedFileId) {
const file = (knowledge?.files ?? []).find((file) => file.id === selectedFileId); const file = (knowledge?.files ?? []).find((file) => file.id === selectedFileId);
if (file) { if (file) {
file.data = file.data ?? { content: '' }; fileSelectHandler(file);
selectedFile = file;
} else { } else {
selectedFile = null; selectedFile = null;
} }
@ -394,7 +397,10 @@
const updateFileContentHandler = async () => { const updateFileContentHandler = async () => {
const fileId = selectedFile.id; const fileId = selectedFile.id;
const content = selectedFile.data.content; const content = selectedFileContent;
// Clear the cache for this file since we're updating it
fileContentCache.delete(fileId);
const res = updateFileDataContentById(localStorage.token, fileId, content).catch((e) => { const res = updateFileDataContentById(localStorage.token, fileId, content).catch((e) => {
toast.error(`${e}`); toast.error(`${e}`);
@ -450,6 +456,29 @@
} }
}; };
const fileSelectHandler = async (file) => {
try {
selectedFile = file;
// Check cache first
if (fileContentCache.has(file.id)) {
selectedFileContent = fileContentCache.get(file.id);
return;
}
const response = await getFileById(localStorage.token, file.id);
if (response) {
selectedFileContent = response.data.content;
// Cache the content
fileContentCache.set(file.id, response.data.content);
} else {
toast.error($i18n.t('No content found in file.'));
}
} catch (e) {
toast.error($i18n.t('Failed to load file content.'));
}
};
const onDragOver = (e) => { const onDragOver = (e) => {
e.preventDefault(); e.preventDefault();
@ -728,7 +757,7 @@
{#key selectedFile.id} {#key selectedFile.id}
<RichTextInput <RichTextInput
className="input-prose-sm" className="input-prose-sm"
bind:value={selectedFile.data.content} bind:value={selectedFileContent}
placeholder={$i18n.t('Add content here')} placeholder={$i18n.t('Add content here')}
preserveBreaks={true} preserveBreaks={true}
/> />
@ -786,7 +815,7 @@
{#key selectedFile.id} {#key selectedFile.id}
<RichTextInput <RichTextInput
className="input-prose-sm" className="input-prose-sm"
bind:value={selectedFile.data.content} bind:value={selectedFileContent}
placeholder={$i18n.t('Add content here')} placeholder={$i18n.t('Add content here')}
preserveBreaks={true} preserveBreaks={true}
/> />