diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index fc9c5543b7..baf1ea4594 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -996,6 +996,12 @@ ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS = ( RAG_FULL_CONTEXT = os.getenv('RAG_FULL_CONTEXT', 'False').lower() == 'true' RAG_FILE_MAX_COUNT = int(os.getenv('RAG_FILE_MAX_COUNT')) if os.getenv('RAG_FILE_MAX_COUNT') else None +KNOWLEDGE_MAX_FILE_COUNT = int(os.getenv('KNOWLEDGE_MAX_FILE_COUNT')) if os.getenv('KNOWLEDGE_MAX_FILE_COUNT') else None +KNOWLEDGE_MAX_FILE_SIZE = int(os.getenv('KNOWLEDGE_MAX_FILE_SIZE')) if os.getenv('KNOWLEDGE_MAX_FILE_SIZE') else None +if KNOWLEDGE_MAX_FILE_COUNT is not None and KNOWLEDGE_MAX_FILE_COUNT <= 0: + raise ValueError('KNOWLEDGE_MAX_FILE_COUNT must be a positive integer or unset') +if KNOWLEDGE_MAX_FILE_SIZE is not None and KNOWLEDGE_MAX_FILE_SIZE <= 0: + raise ValueError('KNOWLEDGE_MAX_FILE_SIZE must be a positive integer or unset') RAG_FILE_MAX_SIZE = int(os.getenv('RAG_FILE_MAX_SIZE')) if os.getenv('RAG_FILE_MAX_SIZE') else None @@ -3013,6 +3019,8 @@ DEFAULT_CONFIG = { 'rag.enable_hybrid_search_enriched_texts': ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS, 'rag.full_context': RAG_FULL_CONTEXT, 'rag.file.max_count': RAG_FILE_MAX_COUNT, + 'knowledge.max_file_count': KNOWLEDGE_MAX_FILE_COUNT, + 'knowledge.max_file_size': KNOWLEDGE_MAX_FILE_SIZE, 'rag.file.max_size': RAG_FILE_MAX_SIZE, 'file.image_compression_width': FILE_IMAGE_COMPRESSION_WIDTH, 'file.image_compression_height': FILE_IMAGE_COMPRESSION_HEIGHT, diff --git a/backend/open_webui/models/knowledge.py b/backend/open_webui/models/knowledge.py index b053344bc9..8e52cea70d 100644 --- a/backend/open_webui/models/knowledge.py +++ b/backend/open_webui/models/knowledge.py @@ -6,6 +6,7 @@ from typing import Optional from open_webui.config import RAG_FILE_CONTENT_SEARCH_MAX_CHARS from open_webui.internal.db import Base, JSONField, get_async_db_context from open_webui.models.access_grants import AccessGrantModel, AccessGrants +from open_webui.models.config import Config from open_webui.models.files import ( File, FileMetadataResponse, @@ -162,10 +163,16 @@ class KnowledgeUserResponse(KnowledgeUserModel): pass +class KnowledgeMeta(BaseModel): + max_file_count: int | None = Field(default=None, gt=0) + max_file_size: int | None = Field(default=None, gt=0) + + class KnowledgeForm(BaseModel): name: str description: str access_grants: Optional[list[dict]] = None + meta: KnowledgeMeta | None = None class FileUserResponse(FileModelResponse): @@ -209,7 +216,8 @@ class KnowledgeTable: async with get_async_db_context(db) as db: knowledge = KnowledgeModel( **{ - **form_data.model_dump(exclude={'access_grants'}), + **form_data.model_dump(exclude={'access_grants', 'meta'}), + 'meta': form_data.meta.model_dump() if form_data.meta else None, 'id': str(uuid.uuid4()), 'user_id': user_id, 'created_at': int(time.time()), @@ -671,6 +679,37 @@ class KnowledgeTable: except Exception: return [] + async def get_file_limits(self, meta: dict | None) -> dict[str, int | None]: + local = meta or {} + counts = [ + int(value) for value in (await Config.get('knowledge.max_file_count'), local.get('max_file_count')) if value + ] + sizes = [ + int(value) for value in (await Config.get('knowledge.max_file_size'), local.get('max_file_size')) if value + ] + return {'max_file_count': min(counts, default=None), 'max_file_size': min(sizes, default=None)} + + async def get_file_count_by_id( + self, + knowledge_id: str, + include_pending: bool = False, + exclude_file_ids: list[str] | None = None, + db: Optional[AsyncSession] = None, + ) -> int: + async with get_async_db_context(db) as db: + linked = select(KnowledgeFile.file_id.label('file_id')).where(KnowledgeFile.knowledge_id == knowledge_id) + if include_pending: + pending = select(File.id.label('file_id')).where( + File.meta['data']['knowledge_id'].as_string() == knowledge_id, + File.data['status'].as_string().in_(['pending', 'processing']), + ) + linked = linked.union(pending) + files = linked.subquery() + stmt = select(func.count()).select_from(files) + if exclude_file_ids: + stmt = stmt.where(files.c.file_id.notin_(exclude_file_ids)) + return (await db.execute(stmt)).scalar_one() + async def add_file_to_knowledge_by_id( self, knowledge_id: str, @@ -679,7 +718,27 @@ class KnowledgeTable: directory_id: Optional[str] = None, db: Optional[AsyncSession] = None, ) -> Optional[KnowledgeFileModel]: + global_limit = await Config.get('knowledge.max_file_count') async with get_async_db_context(db) as db: + # Read the local limit under the same write lock as count + insert. + await db.execute( + update(Knowledge).where(Knowledge.id == knowledge_id).values(updated_at=Knowledge.updated_at) + ) + meta = await db.scalar(select(Knowledge.meta).where(Knowledge.id == knowledge_id)) + local_limit = (meta or {}).get('max_file_count') + limit = min((int(value) for value in (global_limit, local_limit) if value), default=None) + if limit is not None: + existing = await db.scalar(select(KnowledgeFile).filter_by(knowledge_id=knowledge_id, file_id=file_id)) + if existing: + await db.commit() + # Preserve duplicate-insert semantics; callers must not release another request's slot. + return None + count = await db.scalar( + select(func.count()).select_from(KnowledgeFile).where(KnowledgeFile.knowledge_id == knowledge_id) + ) + if count >= int(limit): + await db.rollback() + raise ValueError(f'Knowledge base file limit exceeded ({limit} files maximum).') knowledge_file = KnowledgeFileModel( **{ 'id': str(uuid.uuid4()), @@ -702,6 +761,7 @@ class KnowledgeTable: else: return None except Exception: + await db.rollback() return None async def has_file(self, knowledge_id: str, file_id: str, db: Optional[AsyncSession] = None) -> bool: @@ -758,11 +818,15 @@ class KnowledgeTable: ) -> Optional[KnowledgeModel]: try: async with get_async_db_context(db) as db: + values = form_data.model_dump(exclude={'access_grants', 'meta'}) + if form_data.meta is not None: + meta = await db.scalar(select(Knowledge.meta).where(Knowledge.id == id)) or {} + values['meta'] = {**meta, **form_data.meta.model_dump(exclude_unset=True)} await db.execute( update(Knowledge) .filter_by(id=id) .values( - **form_data.model_dump(exclude={'access_grants'}), + **values, updated_at=int(time.time()), ) ) diff --git a/backend/open_webui/routers/files.py b/backend/open_webui/routers/files.py index 1f2292e753..f583b38880 100644 --- a/backend/open_webui/routers/files.py +++ b/backend/open_webui/routers/files.py @@ -41,6 +41,7 @@ from open_webui.models.knowledge import Knowledges from open_webui.models.users import Users from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT from open_webui.routers.audio import transcribe +from open_webui.routers.knowledge import check_knowledge_upload_limits from open_webui.routers.retrieval import ProcessFileForm, index_file, process_file from open_webui.storage.provider import Storage from open_webui.utils.auth import get_admin_user, get_verified_user @@ -136,6 +137,14 @@ async def process_uploaded_file( ): async def _process_handler(db_session): try: + if file_metadata.get('knowledge_id'): + await check_knowledge_upload_limits( + file_metadata['knowledge_id'], + 1, + file_size=(file_item.meta or {}).get('size'), + exclude_file_ids=[file_item.id], + db=db_session, + ) await Files.update_file_data_by_id(file_item.id, {'status': 'processing'}, db=db_session) content_type = file.content_type @@ -229,12 +238,6 @@ async def process_uploaded_file( ) directory_id = None - await index_file( - request, - ProcessFileForm(file_id=file_item.id, collection_name=knowledge_id), - user=user, - db=db_session, - ) knowledge_file = await Knowledges.add_file_to_knowledge_by_id( knowledge_id=knowledge_id, file_id=file_item.id, @@ -244,6 +247,16 @@ async def process_uploaded_file( ) if not knowledge_file: raise Exception(f'Failed to link file {file_item.id} to knowledge {knowledge_id}') + try: + await index_file( + request, + ProcessFileForm(file_id=file_item.id, collection_name=knowledge_id), + user=user, + db=db_session, + ) + except Exception: + await Knowledges.remove_file_from_knowledge_by_id(knowledge_id, file_item.id, db=db_session) + raise log.info('Linked file %s to knowledge %s', file_item.id, knowledge_id) except Exception as e: log.warning(f'Failed to link file {file_item.id} to knowledge {knowledge_id}: {e}') @@ -357,6 +370,19 @@ async def upload_file_handler( detail=ERROR_MESSAGES.DEFAULT(f'File type {file_extension} is not allowed'), ) + knowledge_id = file_metadata.get('knowledge_id') + if knowledge_id: + knowledge = await Knowledges.get_knowledge_by_id(knowledge_id, db=db) + if not knowledge or not ( + knowledge.user_id == user.id + or user.role == 'admin' + or await AccessGrants.has_access( + user_id=user.id, resource_type='knowledge', resource_id=knowledge_id, permission='write', db=db + ) + ): + raise HTTPException(403, ERROR_MESSAGES.ACCESS_PROHIBITED) + await check_knowledge_upload_limits(knowledge_id, 1, batch=True, file_size=file.size, db=db) + # Prefer readable storage names for admins, but fall back if the filesystem rejects it. id = str(uuid.uuid4()) name = filename @@ -395,6 +421,13 @@ async def upload_file_handler( detail=ERROR_MESSAGES.FILE_TOO_LARGE(size=f'{max_size} MB'), ) + if knowledge_id: + try: + await check_knowledge_upload_limits(knowledge_id, 0, file_size=len(contents), db=db) + except HTTPException: + await asyncio.to_thread(Storage.delete_file, file_path) + raise + # SHA-256 of raw uploaded bytes for incremental sync diffing. # If the client pre-computed and sent file_hash, use that. file_hash = file_metadata.get('file_hash') or await asyncio.to_thread( diff --git a/backend/open_webui/routers/knowledge.py b/backend/open_webui/routers/knowledge.py index 6b13b798cd..7b6e88feec 100644 --- a/backend/open_webui/routers/knowledge.py +++ b/backend/open_webui/routers/knowledge.py @@ -46,7 +46,7 @@ from open_webui.utils.access_control import filter_allowed_access_grants, has_pe from open_webui.utils.access_control.files import has_access_to_file from open_webui.utils.auth import get_admin_user, get_verified_user from open_webui.utils.json_codec import JSONCodec -from pydantic import BaseModel +from pydantic import BaseModel, Field from sqlalchemy.ext.asyncio import AsyncSession log = logging.getLogger(__name__) @@ -1169,6 +1169,12 @@ async def update_knowledge_by_id( detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) + if form_data.meta is not None: + if knowledge.user_id != user.id and user.role != 'admin': + raise HTTPException(403, ERROR_MESSAGES.ACCESS_PROHIBITED) + if is_external_knowledge(knowledge): + external_knowledge_error() + knowledge = await Knowledges.update_knowledge_by_id(id=id, form_data=form_data) if knowledge: # Re-embed knowledge base for semantic search @@ -1402,6 +1408,65 @@ async def get_knowledge_files_by_id( ############################ +async def check_knowledge_upload_limits( + knowledge_id: str, + count: int, + *, + batch: bool = False, + file_size: int | None = None, + exclude_file_ids: list[str] | None = None, + db=None, +) -> None: + if batch: + batch_limit = await Config.get('rag.file.max_count') + if batch_limit is not None and count > int(batch_limit): + raise HTTPException(400, f'You can only upload a maximum of {batch_limit} file(s) per batch.') + + knowledge = await Knowledges.get_knowledge_by_id(knowledge_id, db=db) + if not knowledge: + raise HTTPException(404, ERROR_MESSAGES.NOT_FOUND) + limits = await Knowledges.get_file_limits(knowledge.meta) + + if file_size is not None: + for max_size in (await Config.get('rag.file.max_size'), limits['max_file_size']): + if max_size and file_size > int(max_size) * 1024 * 1024: + raise HTTPException(413, ERROR_MESSAGES.FILE_TOO_LARGE(size=f'{max_size} MB')) + + limit = limits['max_file_count'] + if limit is not None and count > 0: + current_count = await Knowledges.get_file_count_by_id( + knowledge_id, include_pending=True, exclude_file_ids=exclude_file_ids, db=db + ) + if current_count + count > int(limit): + raise HTTPException(400, f'Knowledge base file limit exceeded ({limit} files maximum).') + + +class KnowledgeUploadValidationForm(BaseModel): + count: int = Field(ge=0) + file_size: int | None = Field(default=None, ge=0) + removed_file_ids: list[str] = Field(default_factory=list) + + +@router.post('/{id}/files/validate') +async def validate_knowledge_upload( + id: str, + form_data: KnowledgeUploadValidationForm, + user=Depends(get_verified_user), + db: AsyncSession = Depends(get_async_session), +): + await _verify_knowledge_write_access(id, user, db) + # This is a preflight only. Actual additions recheck capacity without removal credits. + await check_knowledge_upload_limits( + id, + form_data.count, + batch=True, + file_size=form_data.file_size, + exclude_file_ids=form_data.removed_file_ids, + db=db, + ) + return {'status': True} + + class KnowledgeFileIdForm(BaseModel): file_id: str directory_id: Optional[str] = None @@ -1462,24 +1527,34 @@ async def add_file_to_knowledge_by_id( detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - # Add content to the vector database + already_linked = await Knowledges.has_file(knowledge_id=id, file_id=form_data.file_id, db=db) + if not already_linked: + await check_knowledge_upload_limits( + id, 1, batch=True, file_size=(file.meta or {}).get('size', 0), exclude_file_ids=[form_data.file_id], db=db + ) + + linked = None try: + # Claim capacity atomically before indexing into the knowledge collection. + if not already_linked: + linked = await Knowledges.add_file_to_knowledge_by_id( + knowledge_id=id, + file_id=form_data.file_id, + user_id=user.id, + directory_id=form_data.directory_id, + db=db, + ) + if not linked: + raise ValueError(f'Failed to link file {form_data.file_id} to knowledge {id}') await process_file( request, ProcessFileForm(file_id=form_data.file_id, collection_name=id), user=user, db=db, ) - - # Add file to knowledge base - await Knowledges.add_file_to_knowledge_by_id( - knowledge_id=id, - file_id=form_data.file_id, - user_id=user.id, - directory_id=form_data.directory_id, - db=db, - ) except Exception as e: + if linked: + await Knowledges.remove_file_from_knowledge_by_id(id, form_data.file_id, db=db) log.debug(e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -1943,6 +2018,18 @@ async def sync_knowledge_diff( if key not in manifest_keys: deleted.append({'file_id': file_info['file_id'], 'filename': key[1]}) + changed_keys = {(entry['path'], entry['filename']) for entry in added + modified} + await check_knowledge_upload_limits( + id, + len(added) + len(modified), + file_size=max( + (entry.size for entry in form_data.manifest if (entry.path, entry.filename) in changed_keys), default=0 + ), + batch=True, + exclude_file_ids=[entry['file_id'] for entry in deleted] + [entry['stale_file_id'] for entry in modified], + db=db, + ) + # ── Diff directories ── required_directory_paths: set[str] = set() for entry in form_data.manifest: @@ -2069,6 +2156,10 @@ async def add_files_to_knowledge_batch( detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) + batch_limit = await Config.get('rag.file.max_count') + if batch_limit is not None and len(form_data) > int(batch_limit): + raise HTTPException(400, f'You can only upload a maximum of {batch_limit} file(s) per batch.') + for directory_id in {form.directory_id for form in form_data if form.directory_id}: await _verify_directory_in_knowledge(id, directory_id, db, detail='Target directory not found.') @@ -2112,8 +2203,29 @@ async def add_files_to_knowledge_batch( new_file_ids = {form.file_id for form in new_entries} files = [f for f in files if f.id in new_file_ids] - # Process files + await check_knowledge_upload_limits( + id, + len(files), + file_size=max(((file.meta or {}).get('size', 0) or 0 for file in files), default=0), + exclude_file_ids=list(new_file_ids), + db=db, + ) + + # Claim slots before embedding; release them if processing fails. + reserved_ids = [] + dir_map = {form.file_id: form.directory_id for form in new_entries} try: + for file in files: + linked = await Knowledges.add_file_to_knowledge_by_id( + knowledge_id=id, + file_id=file.id, + user_id=user.id, + directory_id=dir_map.get(file.id), + db=db, + ) + if not linked: + raise ValueError(f'Failed to link file {file.id} to knowledge {id}') + reserved_ids.append(file.id) result = await process_files_batch( request=request, form_data=BatchProcessFilesForm(files=files, collection_name=id), @@ -2121,20 +2233,14 @@ async def add_files_to_knowledge_batch( db=db, ) except Exception as e: + for file_id in reserved_ids: + await Knowledges.remove_file_from_knowledge_by_id(id, file_id, db=db) log.error(f'add_files_to_knowledge_batch: Exception occurred: {e}', exc_info=True) raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) - # Only add files that were successfully processed successful_file_ids = [r.file_id for r in result.results if r.status == 'completed'] - dir_map = {form.file_id: form.directory_id for form in new_entries} - for file_id in successful_file_ids: - await Knowledges.add_file_to_knowledge_by_id( - knowledge_id=id, - file_id=file_id, - user_id=user.id, - directory_id=dir_map.get(file_id), - db=db, - ) + for file_id in set(reserved_ids) - set(successful_file_ids): + await Knowledges.remove_file_from_knowledge_by_id(id, file_id, db=db) # If there were any errors, include them in the response if result.errors: diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py index d2a6857c06..69005a3b22 100644 --- a/backend/open_webui/routers/retrieval.py +++ b/backend/open_webui/routers/retrieval.py @@ -305,6 +305,8 @@ RETRIEVAL_CONFIG_KEYS = { 'FILE_IMAGE_COMPRESSION_HEIGHT': 'file.image_compression_height', 'FILE_IMAGE_COMPRESSION_WIDTH': 'file.image_compression_width', 'FILE_MAX_COUNT': 'rag.file.max_count', + 'KNOWLEDGE_MAX_FILE_COUNT': 'knowledge.max_file_count', + 'KNOWLEDGE_MAX_FILE_SIZE': 'knowledge.max_file_size', 'FILE_MAX_SIZE': 'rag.file.max_size', 'FIRECRAWL_API_BASE_URL': 'web.loader.firecrawl_api_url', 'FIRECRAWL_API_KEY': 'web.loader.firecrawl_api_key', @@ -684,6 +686,8 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)): # File upload settings 'FILE_MAX_SIZE': config['rag.file.max_size'], 'FILE_MAX_COUNT': config['rag.file.max_count'], + 'KNOWLEDGE_MAX_FILE_COUNT': config['knowledge.max_file_count'], + 'KNOWLEDGE_MAX_FILE_SIZE': config['knowledge.max_file_size'], 'FILE_IMAGE_COMPRESSION_WIDTH': config['file.image_compression_width'], 'FILE_IMAGE_COMPRESSION_HEIGHT': config['file.image_compression_height'], 'ALLOWED_FILE_EXTENSIONS': config['rag.file.allowed_extensions'], @@ -935,6 +939,8 @@ class ConfigForm(BaseModel): # File upload settings FILE_MAX_SIZE: Union[int, str | None] = None FILE_MAX_COUNT: Union[int, str | None] = None + KNOWLEDGE_MAX_FILE_COUNT: Union[int, str | None] = None + KNOWLEDGE_MAX_FILE_SIZE: Union[int, str | None] = None FILE_IMAGE_COMPRESSION_WIDTH: Union[int, str | None] = None FILE_IMAGE_COMPRESSION_HEIGHT: Union[int, str | None] = None ALLOWED_FILE_EXTENSIONS: list[str] | None = None @@ -998,10 +1004,33 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend } if form_data.RAG_TOKENIZER_MODEL is not None: updates['rag.tokenizer_model'] = form_data.RAG_TOKENIZER_MODEL.strip() - for field in ('FILE_MAX_SIZE', 'FILE_MAX_COUNT', 'FILE_IMAGE_COMPRESSION_WIDTH', 'FILE_IMAGE_COMPRESSION_HEIGHT'): + for field in ( + 'FILE_MAX_SIZE', + 'FILE_MAX_COUNT', + 'KNOWLEDGE_MAX_FILE_COUNT', + 'KNOWLEDGE_MAX_FILE_SIZE', + 'FILE_IMAGE_COMPRESSION_WIDTH', + 'FILE_IMAGE_COMPRESSION_HEIGHT', + ): if getattr(form_data, field) == '': updates[RETRIEVAL_CONFIG_KEYS[field]] = None + for field, label in ( + ('KNOWLEDGE_MAX_FILE_COUNT', 'Knowledge file count limit'), + ('KNOWLEDGE_MAX_FILE_SIZE', 'Knowledge file size limit'), + ): + value = getattr(form_data, field) + if field in form_data.model_fields_set and value is None: + updates[RETRIEVAL_CONFIG_KEYS[field]] = None + if value not in (None, ''): + try: + value = int(value) + if value <= 0: + raise ValueError + except (TypeError, ValueError): + raise HTTPException(400, f'{label} must be a positive integer.') + updates[RETRIEVAL_CONFIG_KEYS[field]] = value + # Unload the previous internal reranker before applying its new settings. if config['rag.reranking_engine'] == '': request.app.state.rf = None @@ -1116,6 +1145,8 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend # File upload settings 'FILE_MAX_SIZE': config['rag.file.max_size'], 'FILE_MAX_COUNT': config['rag.file.max_count'], + 'KNOWLEDGE_MAX_FILE_COUNT': config['knowledge.max_file_count'], + 'KNOWLEDGE_MAX_FILE_SIZE': config['knowledge.max_file_size'], 'FILE_IMAGE_COMPRESSION_WIDTH': config['file.image_compression_width'], 'FILE_IMAGE_COMPRESSION_HEIGHT': config['file.image_compression_height'], 'ALLOWED_FILE_EXTENSIONS': config['rag.file.allowed_extensions'], diff --git a/src/lib/apis/knowledge/index.ts b/src/lib/apis/knowledge/index.ts index d3a1780e9a..c6835ff208 100644 --- a/src/lib/apis/knowledge/index.ts +++ b/src/lib/apis/knowledge/index.ts @@ -1,10 +1,35 @@ import { WEBUI_API_BASE_URL } from '$lib/constants'; +type KnowledgeMeta = { + max_file_count: number | null; + max_file_size: number | null; +}; + +export const validateKnowledgeUpload = async ( + token: string, + id: string, + count: number, + removedFileIds: string[] = [], + fileSize?: number +) => { + const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/${id}/files/validate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ count, removed_file_ids: removedFileIds, file_size: fileSize }) + }); + if (!res.ok) throw (await res.json()).detail; + return res.json(); +}; + export const createNewKnowledge = async ( token: string, name: string, description: string, - accessGrants: object[] + accessGrants: object[], + meta?: KnowledgeMeta ) => { let error = null; @@ -18,7 +43,8 @@ export const createNewKnowledge = async ( body: JSON.stringify({ name: name, description: description, - access_grants: accessGrants + access_grants: accessGrants, + meta: meta }) }) .then(async (res) => { @@ -605,6 +631,7 @@ export const streamPendingKnowledgeFiles = async (token: string, id: string) => }; type KnowledgeUpdateForm = { + meta?: KnowledgeMeta; name?: string; description?: string; data?: object; @@ -623,7 +650,8 @@ export const updateKnowledgeById = async (token: string, id: string, form: Knowl body: JSON.stringify({ name: form?.name ? form.name : undefined, description: form?.description ? form.description : undefined, - data: form?.data ? form.data : undefined + data: form?.data ? form.data : undefined, + meta: form.meta }) }) .then(async (res) => { diff --git a/src/lib/components/admin/Settings/Documents.svelte b/src/lib/components/admin/Settings/Documents.svelte index 9ac3656611..9d5678e1a3 100644 --- a/src/lib/components/admin/Settings/Documents.svelte +++ b/src/lib/components/admin/Settings/Documents.svelte @@ -282,6 +282,8 @@ // can distinguish "clear this field" from "don't change this field" FILE_MAX_SIZE: RAGConfig.FILE_MAX_SIZE ?? '', FILE_MAX_COUNT: RAGConfig.FILE_MAX_COUNT ?? '', + KNOWLEDGE_MAX_FILE_COUNT: RAGConfig.KNOWLEDGE_MAX_FILE_COUNT ?? '', + KNOWLEDGE_MAX_FILE_SIZE: RAGConfig.KNOWLEDGE_MAX_FILE_SIZE ?? '', FILE_IMAGE_COMPRESSION_WIDTH: RAGConfig.FILE_IMAGE_COMPRESSION_WIDTH ?? '', FILE_IMAGE_COMPRESSION_HEIGHT: RAGConfig.FILE_IMAGE_COMPRESSION_HEIGHT ?? '', ALLOWED_FILE_EXTENSIONS: RAGConfig.ALLOWED_FILE_EXTENSIONS.split(',') @@ -1591,6 +1593,37 @@ +
{$i18n.t('Maximum total files.')}
+{$i18n.t('Maximum size per file.')}
++ {$i18n.t('Global limits always apply.')} +
+