refac
This commit is contained in:
+125
-217
@@ -73,14 +73,14 @@ def _is_text_file(file_path: str, chunk_size: int = 8192) -> bool:
|
||||
"""
|
||||
try:
|
||||
resolved = Storage.get_file(file_path)
|
||||
with open(resolved, "rb") as f:
|
||||
with open(resolved, 'rb') as f:
|
||||
chunk = f.read(chunk_size)
|
||||
if not chunk:
|
||||
return False
|
||||
# Null bytes are a strong indicator of binary content
|
||||
if b"\x00" in chunk:
|
||||
if b'\x00' in chunk:
|
||||
return False
|
||||
chunk.decode("utf-8")
|
||||
chunk.decode('utf-8')
|
||||
return True
|
||||
except (UnicodeDecodeError, Exception):
|
||||
return False
|
||||
@@ -100,31 +100,25 @@ def process_uploaded_file(
|
||||
content_type = file.content_type
|
||||
|
||||
# Detect mis-labeled text files (e.g. .ts → video/mp2t)
|
||||
if content_type and content_type.startswith(("image/", "video/")):
|
||||
if content_type and content_type.startswith(('image/', 'video/')):
|
||||
if _is_text_file(file_path):
|
||||
content_type = "text/plain"
|
||||
content_type = 'text/plain'
|
||||
|
||||
if content_type:
|
||||
stt_supported_content_types = getattr(
|
||||
request.app.state.config, "STT_SUPPORTED_CONTENT_TYPES", []
|
||||
)
|
||||
stt_supported_content_types = getattr(request.app.state.config, 'STT_SUPPORTED_CONTENT_TYPES', [])
|
||||
|
||||
if strict_match_mime_type(stt_supported_content_types, content_type):
|
||||
file_path_processed = Storage.get_file(file_path)
|
||||
result = transcribe(
|
||||
request, file_path_processed, file_metadata, user
|
||||
)
|
||||
result = transcribe(request, file_path_processed, file_metadata, user)
|
||||
|
||||
process_file(
|
||||
request,
|
||||
ProcessFileForm(
|
||||
file_id=file_item.id, content=result.get("text", "")
|
||||
),
|
||||
ProcessFileForm(file_id=file_item.id, content=result.get('text', '')),
|
||||
user=user,
|
||||
db=db_session,
|
||||
)
|
||||
elif (not content_type.startswith(("image/", "video/"))) or (
|
||||
request.app.state.config.CONTENT_EXTRACTION_ENGINE == "external"
|
||||
elif (not content_type.startswith(('image/', 'video/'))) or (
|
||||
request.app.state.config.CONTENT_EXTRACTION_ENGINE == 'external'
|
||||
):
|
||||
process_file(
|
||||
request,
|
||||
@@ -133,13 +127,9 @@ def process_uploaded_file(
|
||||
db=db_session,
|
||||
)
|
||||
else:
|
||||
raise Exception(
|
||||
f"File type {content_type} is not supported for processing"
|
||||
)
|
||||
raise Exception(f'File type {content_type} is not supported for processing')
|
||||
else:
|
||||
log.info(
|
||||
f"File type {file.content_type} is not provided, but trying to process anyway"
|
||||
)
|
||||
log.info(f'File type {file.content_type} is not provided, but trying to process anyway')
|
||||
process_file(
|
||||
request,
|
||||
ProcessFileForm(file_id=file_item.id),
|
||||
@@ -148,12 +138,12 @@ def process_uploaded_file(
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"Error processing file: {file_item.id}")
|
||||
log.error(f'Error processing file: {file_item.id}')
|
||||
Files.update_file_data_by_id(
|
||||
file_item.id,
|
||||
{
|
||||
"status": "failed",
|
||||
"error": str(e.detail) if hasattr(e, "detail") else str(e),
|
||||
'status': 'failed',
|
||||
'error': str(e.detail) if hasattr(e, 'detail') else str(e),
|
||||
},
|
||||
db=db_session,
|
||||
)
|
||||
@@ -165,7 +155,7 @@ def process_uploaded_file(
|
||||
_process_handler(db_session)
|
||||
|
||||
|
||||
@router.post("/", response_model=FileModelResponse)
|
||||
@router.post('/', response_model=FileModelResponse)
|
||||
def upload_file(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
@@ -198,7 +188,7 @@ def upload_file_handler(
|
||||
background_tasks: Optional[BackgroundTasks] = None,
|
||||
db: Optional[Session] = None,
|
||||
):
|
||||
log.info(f"file.content_type: {file.content_type} {process}")
|
||||
log.info(f'file.content_type: {file.content_type} {process}')
|
||||
|
||||
if isinstance(metadata, str):
|
||||
try:
|
||||
@@ -206,7 +196,7 @@ def upload_file_handler(
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT("Invalid metadata format"),
|
||||
detail=ERROR_MESSAGES.DEFAULT('Invalid metadata format'),
|
||||
)
|
||||
file_metadata = metadata if metadata else {}
|
||||
|
||||
@@ -216,7 +206,7 @@ def upload_file_handler(
|
||||
|
||||
file_extension = os.path.splitext(filename)[1]
|
||||
# Remove the leading dot from the file extension
|
||||
file_extension = file_extension[1:] if file_extension else ""
|
||||
file_extension = file_extension[1:] if file_extension else ''
|
||||
|
||||
if process and request.app.state.config.ALLOWED_FILE_EXTENSIONS:
|
||||
request.app.state.config.ALLOWED_FILE_EXTENSIONS = [
|
||||
@@ -226,23 +216,21 @@ def upload_file_handler(
|
||||
if file_extension not in request.app.state.config.ALLOWED_FILE_EXTENSIONS:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT(
|
||||
f"File type {file_extension} is not allowed"
|
||||
),
|
||||
detail=ERROR_MESSAGES.DEFAULT(f'File type {file_extension} is not allowed'),
|
||||
)
|
||||
|
||||
# replace filename with uuid
|
||||
id = str(uuid.uuid4())
|
||||
name = filename
|
||||
filename = f"{id}_{filename}"
|
||||
filename = f'{id}_{filename}'
|
||||
contents, file_path = Storage.upload_file(
|
||||
file.file,
|
||||
filename,
|
||||
{
|
||||
"OpenWebUI-User-Email": user.email,
|
||||
"OpenWebUI-User-Id": user.id,
|
||||
"OpenWebUI-User-Name": user.name,
|
||||
"OpenWebUI-File-Id": id,
|
||||
'OpenWebUI-User-Email': user.email,
|
||||
'OpenWebUI-User-Id': user.id,
|
||||
'OpenWebUI-User-Name': user.name,
|
||||
'OpenWebUI-File-Id': id,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -250,35 +238,27 @@ def upload_file_handler(
|
||||
user.id,
|
||||
FileForm(
|
||||
**{
|
||||
"id": id,
|
||||
"filename": name,
|
||||
"path": file_path,
|
||||
"data": {
|
||||
**({"status": "pending"} if process else {}),
|
||||
'id': id,
|
||||
'filename': name,
|
||||
'path': file_path,
|
||||
'data': {
|
||||
**({'status': 'pending'} if process else {}),
|
||||
},
|
||||
"meta": {
|
||||
"name": name,
|
||||
"content_type": (
|
||||
file.content_type
|
||||
if isinstance(file.content_type, str)
|
||||
else None
|
||||
),
|
||||
"size": len(contents),
|
||||
"data": file_metadata,
|
||||
'meta': {
|
||||
'name': name,
|
||||
'content_type': (file.content_type if isinstance(file.content_type, str) else None),
|
||||
'size': len(contents),
|
||||
'data': file_metadata,
|
||||
},
|
||||
}
|
||||
),
|
||||
db=db,
|
||||
)
|
||||
|
||||
if "channel_id" in file_metadata:
|
||||
channel = Channels.get_channel_by_id_and_user_id(
|
||||
file_metadata["channel_id"], user.id, db=db
|
||||
)
|
||||
if 'channel_id' in file_metadata:
|
||||
channel = Channels.get_channel_by_id_and_user_id(file_metadata['channel_id'], user.id, db=db)
|
||||
if channel:
|
||||
Channels.add_file_to_channel_by_id(
|
||||
channel.id, file_item.id, user.id, db=db
|
||||
)
|
||||
Channels.add_file_to_channel_by_id(channel.id, file_item.id, user.id, db=db)
|
||||
|
||||
if process:
|
||||
if background_tasks and process_in_background:
|
||||
@@ -291,7 +271,7 @@ def upload_file_handler(
|
||||
file_metadata,
|
||||
user,
|
||||
)
|
||||
return {"status": True, **file_item.model_dump()}
|
||||
return {'status': True, **file_item.model_dump()}
|
||||
else:
|
||||
process_uploaded_file(
|
||||
request,
|
||||
@@ -302,14 +282,14 @@ def upload_file_handler(
|
||||
user,
|
||||
db=db,
|
||||
)
|
||||
return {"status": True, **file_item.model_dump()}
|
||||
return {'status': True, **file_item.model_dump()}
|
||||
else:
|
||||
if file_item:
|
||||
return file_item
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT("Error uploading file"),
|
||||
detail=ERROR_MESSAGES.DEFAULT('Error uploading file'),
|
||||
)
|
||||
|
||||
except HTTPException as e:
|
||||
@@ -318,7 +298,7 @@ def upload_file_handler(
|
||||
log.exception(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT("Error uploading file"),
|
||||
detail=ERROR_MESSAGES.DEFAULT('Error uploading file'),
|
||||
)
|
||||
|
||||
|
||||
@@ -330,26 +310,22 @@ def upload_file_handler(
|
||||
PAGE_SIZE = 50
|
||||
|
||||
|
||||
@router.get("/", response_model=FileListResponse)
|
||||
@router.get('/', response_model=FileListResponse)
|
||||
async def list_files(
|
||||
user=Depends(get_verified_user),
|
||||
page: int = Query(1, ge=1, description="Page number (1-indexed)"),
|
||||
page: int = Query(1, ge=1, description='Page number (1-indexed)'),
|
||||
content: bool = Query(True),
|
||||
db: Session = Depends(get_session),
|
||||
):
|
||||
skip = (page - 1) * PAGE_SIZE
|
||||
user_id = (
|
||||
None if (user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL) else user.id
|
||||
)
|
||||
user_id = None if (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL) else user.id
|
||||
|
||||
result = Files.get_file_list(
|
||||
user_id=user_id, skip=skip, limit=PAGE_SIZE, db=db
|
||||
)
|
||||
result = Files.get_file_list(user_id=user_id, skip=skip, limit=PAGE_SIZE, db=db)
|
||||
|
||||
if not content:
|
||||
for file in result.items:
|
||||
if file.data and "content" in file.data:
|
||||
del file.data["content"]
|
||||
if file.data and 'content' in file.data:
|
||||
del file.data['content']
|
||||
|
||||
return result
|
||||
|
||||
@@ -359,17 +335,15 @@ async def list_files(
|
||||
############################
|
||||
|
||||
|
||||
@router.get("/search", response_model=list[FileModelResponse])
|
||||
@router.get('/search', response_model=list[FileModelResponse])
|
||||
async def search_files(
|
||||
filename: str = Query(
|
||||
...,
|
||||
description="Filename pattern to search for. Supports wildcards such as '*.txt'",
|
||||
),
|
||||
content: bool = Query(True),
|
||||
skip: int = Query(0, ge=0, description="Number of files to skip"),
|
||||
limit: int = Query(
|
||||
100, ge=1, le=1000, description="Maximum number of files to return"
|
||||
),
|
||||
skip: int = Query(0, ge=0, description='Number of files to skip'),
|
||||
limit: int = Query(100, ge=1, le=1000, description='Maximum number of files to return'),
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
):
|
||||
@@ -378,9 +352,7 @@ async def search_files(
|
||||
Uses SQL-based filtering with pagination for better performance.
|
||||
"""
|
||||
# Determine user_id: null for admin with bypass (search all), user.id otherwise
|
||||
user_id = (
|
||||
None if (user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL) else user.id
|
||||
)
|
||||
user_id = None if (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL) else user.id
|
||||
|
||||
# Use optimized database query with pagination
|
||||
files = Files.search_files(
|
||||
@@ -394,13 +366,13 @@ async def search_files(
|
||||
if not files:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="No files found matching the pattern.",
|
||||
detail='No files found matching the pattern.',
|
||||
)
|
||||
|
||||
if not content:
|
||||
for file in files:
|
||||
if file.data and "content" in file.data:
|
||||
del file.data["content"]
|
||||
if file.data and 'content' in file.data:
|
||||
del file.data['content']
|
||||
|
||||
return files
|
||||
|
||||
@@ -410,10 +382,8 @@ async def search_files(
|
||||
############################
|
||||
|
||||
|
||||
@router.delete("/all")
|
||||
async def delete_all_files(
|
||||
user=Depends(get_admin_user), db: Session = Depends(get_session)
|
||||
):
|
||||
@router.delete('/all')
|
||||
async def delete_all_files(user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
result = Files.delete_all_files(db=db)
|
||||
if result:
|
||||
try:
|
||||
@@ -421,16 +391,16 @@ async def delete_all_files(
|
||||
VECTOR_DB_CLIENT.reset()
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
log.error("Error deleting files")
|
||||
log.error('Error deleting files')
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT("Error deleting files"),
|
||||
detail=ERROR_MESSAGES.DEFAULT('Error deleting files'),
|
||||
)
|
||||
return {"message": "All files deleted successfully"}
|
||||
return {'message': 'All files deleted successfully'}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT("Error deleting files"),
|
||||
detail=ERROR_MESSAGES.DEFAULT('Error deleting files'),
|
||||
)
|
||||
|
||||
|
||||
@@ -439,10 +409,8 @@ async def delete_all_files(
|
||||
############################
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=Optional[FileModel])
|
||||
async def get_file_by_id(
|
||||
id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)
|
||||
):
|
||||
@router.get('/{id}', response_model=Optional[FileModel])
|
||||
async def get_file_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
@@ -451,11 +419,7 @@ async def get_file_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if (
|
||||
file.user_id == user.id
|
||||
or user.role == "admin"
|
||||
or has_access_to_file(id, "read", user, db=db)
|
||||
):
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
|
||||
return file
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -464,7 +428,7 @@ async def get_file_by_id(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{id}/process/status")
|
||||
@router.get('/{id}/process/status')
|
||||
async def get_file_process_status(
|
||||
id: str,
|
||||
stream: bool = Query(False),
|
||||
@@ -479,11 +443,7 @@ async def get_file_process_status(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if (
|
||||
file.user_id == user.id
|
||||
or user.role == "admin"
|
||||
or has_access_to_file(id, "read", user, db=db)
|
||||
):
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
|
||||
if stream:
|
||||
MAX_FILE_PROCESSING_DURATION = 3600 * 2
|
||||
|
||||
@@ -494,32 +454,32 @@ async def get_file_process_status(
|
||||
for _ in range(MAX_FILE_PROCESSING_DURATION):
|
||||
file_item = Files.get_file_by_id(file_id) # Creates own session
|
||||
if file_item:
|
||||
data = file_item.model_dump().get("data", {})
|
||||
status = data.get("status")
|
||||
data = file_item.model_dump().get('data', {})
|
||||
status = data.get('status')
|
||||
|
||||
if status:
|
||||
event = {"status": status}
|
||||
if status == "failed":
|
||||
event["error"] = data.get("error")
|
||||
event = {'status': status}
|
||||
if status == 'failed':
|
||||
event['error'] = data.get('error')
|
||||
|
||||
yield f"data: {json.dumps(event)}\n\n"
|
||||
if status in ("completed", "failed"):
|
||||
yield f'data: {json.dumps(event)}\n\n'
|
||||
if status in ('completed', 'failed'):
|
||||
break
|
||||
else:
|
||||
# Legacy
|
||||
break
|
||||
else:
|
||||
yield f"data: {json.dumps({'status': 'not_found'})}\n\n"
|
||||
yield f'data: {json.dumps({"status": "not_found"})}\n\n'
|
||||
break
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
return StreamingResponse(
|
||||
event_stream(file.id),
|
||||
media_type="text/event-stream",
|
||||
media_type='text/event-stream',
|
||||
)
|
||||
else:
|
||||
return {"status": file.data.get("status", "pending")}
|
||||
return {'status': file.data.get('status', 'pending')}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -532,10 +492,8 @@ async def get_file_process_status(
|
||||
############################
|
||||
|
||||
|
||||
@router.get("/{id}/data/content")
|
||||
async def get_file_data_content_by_id(
|
||||
id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)
|
||||
):
|
||||
@router.get('/{id}/data/content')
|
||||
async def get_file_data_content_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
@@ -544,12 +502,8 @@ async def get_file_data_content_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if (
|
||||
file.user_id == user.id
|
||||
or user.role == "admin"
|
||||
or has_access_to_file(id, "read", user, db=db)
|
||||
):
|
||||
return {"content": file.data.get("content", "")}
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
|
||||
return {'content': file.data.get('content', '')}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -566,7 +520,7 @@ class ContentForm(BaseModel):
|
||||
content: str
|
||||
|
||||
|
||||
@router.post("/{id}/data/content/update")
|
||||
@router.post('/{id}/data/content/update')
|
||||
def update_file_data_content_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
@@ -582,11 +536,7 @@ def update_file_data_content_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if (
|
||||
file.user_id == user.id
|
||||
or user.role == "admin"
|
||||
or has_access_to_file(id, "write", user, db=db)
|
||||
):
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'write', user, db=db):
|
||||
try:
|
||||
process_file(
|
||||
request,
|
||||
@@ -597,7 +547,7 @@ def update_file_data_content_by_id(
|
||||
file = Files.get_file_by_id(id=id, db=db)
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
log.error(f"Error processing file: {file.id}")
|
||||
log.error(f'Error processing file: {file.id}')
|
||||
|
||||
# Propagate content change to all knowledge collections referencing
|
||||
# this file. Without this the old embeddings remain in the knowledge
|
||||
@@ -606,9 +556,7 @@ def update_file_data_content_by_id(
|
||||
for knowledge in knowledges:
|
||||
try:
|
||||
# Remove old embeddings for this file from the KB collection
|
||||
VECTOR_DB_CLIENT.delete(
|
||||
collection_name=knowledge.id, filter={"file_id": id}
|
||||
)
|
||||
VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'file_id': id})
|
||||
# Re-add from the now-updated file-{file_id} collection
|
||||
process_file(
|
||||
request,
|
||||
@@ -617,12 +565,9 @@ def update_file_data_content_by_id(
|
||||
db=db,
|
||||
)
|
||||
except Exception as e:
|
||||
log.warning(
|
||||
f"Failed to update knowledge {knowledge.id} after "
|
||||
f"content change for file {id}: {e}"
|
||||
)
|
||||
log.warning(f'Failed to update knowledge {knowledge.id} after content change for file {id}: {e}')
|
||||
|
||||
return {"content": file.data.get("content", "")}
|
||||
return {'content': file.data.get('content', '')}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -635,7 +580,7 @@ def update_file_data_content_by_id(
|
||||
############################
|
||||
|
||||
|
||||
@router.get("/{id}/content")
|
||||
@router.get('/{id}/content')
|
||||
async def get_file_content_by_id(
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
@@ -650,11 +595,7 @@ async def get_file_content_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if (
|
||||
file.user_id == user.id
|
||||
or user.role == "admin"
|
||||
or has_access_to_file(id, "read", user, db=db)
|
||||
):
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
|
||||
try:
|
||||
file_path = Storage.get_file(file.path)
|
||||
file_path = Path(file_path)
|
||||
@@ -662,30 +603,22 @@ async def get_file_content_by_id(
|
||||
# Check if the file already exists in the cache
|
||||
if file_path.is_file():
|
||||
# Handle Unicode filenames
|
||||
filename = file.meta.get("name", file.filename)
|
||||
filename = file.meta.get('name', file.filename)
|
||||
encoded_filename = quote(filename) # RFC5987 encoding
|
||||
|
||||
content_type = file.meta.get("content_type")
|
||||
filename = file.meta.get("name", file.filename)
|
||||
content_type = file.meta.get('content_type')
|
||||
filename = file.meta.get('name', file.filename)
|
||||
encoded_filename = quote(filename)
|
||||
headers = {}
|
||||
|
||||
if attachment:
|
||||
headers["Content-Disposition"] = (
|
||||
f"attachment; filename*=UTF-8''{encoded_filename}"
|
||||
)
|
||||
headers['Content-Disposition'] = f"attachment; filename*=UTF-8''{encoded_filename}"
|
||||
else:
|
||||
if content_type == "application/pdf" or filename.lower().endswith(
|
||||
".pdf"
|
||||
):
|
||||
headers["Content-Disposition"] = (
|
||||
f"inline; filename*=UTF-8''{encoded_filename}"
|
||||
)
|
||||
content_type = "application/pdf"
|
||||
elif content_type != "text/plain":
|
||||
headers["Content-Disposition"] = (
|
||||
f"attachment; filename*=UTF-8''{encoded_filename}"
|
||||
)
|
||||
if content_type == 'application/pdf' or filename.lower().endswith('.pdf'):
|
||||
headers['Content-Disposition'] = f"inline; filename*=UTF-8''{encoded_filename}"
|
||||
content_type = 'application/pdf'
|
||||
elif content_type != 'text/plain':
|
||||
headers['Content-Disposition'] = f"attachment; filename*=UTF-8''{encoded_filename}"
|
||||
|
||||
return FileResponse(file_path, headers=headers, media_type=content_type)
|
||||
|
||||
@@ -698,10 +631,10 @@ async def get_file_content_by_id(
|
||||
raise e
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
log.error("Error getting file content")
|
||||
log.error('Error getting file content')
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT("Error getting file content"),
|
||||
detail=ERROR_MESSAGES.DEFAULT('Error getting file content'),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -710,10 +643,8 @@ async def get_file_content_by_id(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{id}/content/html")
|
||||
async def get_html_file_content_by_id(
|
||||
id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)
|
||||
):
|
||||
@router.get('/{id}/content/html')
|
||||
async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
@@ -723,24 +654,20 @@ async def get_html_file_content_by_id(
|
||||
)
|
||||
|
||||
file_user = Users.get_user_by_id(file.user_id, db=db)
|
||||
if not file_user.role == "admin":
|
||||
if not file_user.role == 'admin':
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if (
|
||||
file.user_id == user.id
|
||||
or user.role == "admin"
|
||||
or has_access_to_file(id, "read", user, db=db)
|
||||
):
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
|
||||
try:
|
||||
file_path = Storage.get_file(file.path)
|
||||
file_path = Path(file_path)
|
||||
|
||||
# Check if the file already exists in the cache
|
||||
if file_path.is_file():
|
||||
log.info(f"file_path: {file_path}")
|
||||
log.info(f'file_path: {file_path}')
|
||||
return FileResponse(file_path)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -751,10 +678,10 @@ async def get_html_file_content_by_id(
|
||||
raise e
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
log.error("Error getting file content")
|
||||
log.error('Error getting file content')
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT("Error getting file content"),
|
||||
detail=ERROR_MESSAGES.DEFAULT('Error getting file content'),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -763,10 +690,8 @@ async def get_html_file_content_by_id(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{id}/content/{file_name}")
|
||||
async def get_file_content_by_id(
|
||||
id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)
|
||||
):
|
||||
@router.get('/{id}/content/{file_name}')
|
||||
async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
@@ -775,19 +700,13 @@ async def get_file_content_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if (
|
||||
file.user_id == user.id
|
||||
or user.role == "admin"
|
||||
or has_access_to_file(id, "read", user, db=db)
|
||||
):
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
|
||||
file_path = file.path
|
||||
|
||||
# Handle Unicode filenames
|
||||
filename = file.meta.get("name", file.filename)
|
||||
filename = file.meta.get('name', file.filename)
|
||||
encoded_filename = quote(filename) # RFC5987 encoding
|
||||
headers = {
|
||||
"Content-Disposition": f"attachment; filename*=UTF-8''{encoded_filename}"
|
||||
}
|
||||
headers = {'Content-Disposition': f"attachment; filename*=UTF-8''{encoded_filename}"}
|
||||
|
||||
if file_path:
|
||||
file_path = Storage.get_file(file_path)
|
||||
@@ -803,16 +722,16 @@ async def get_file_content_by_id(
|
||||
)
|
||||
else:
|
||||
# File path doesn’t exist, return the content as .txt if possible
|
||||
file_content = file.content.get("content", "")
|
||||
file_content = file.content.get('content', '')
|
||||
file_name = file.filename
|
||||
|
||||
# Create a generator that encodes the file content
|
||||
def generator():
|
||||
yield file_content.encode("utf-8")
|
||||
yield file_content.encode('utf-8')
|
||||
|
||||
return StreamingResponse(
|
||||
generator(),
|
||||
media_type="text/plain",
|
||||
media_type='text/plain',
|
||||
headers=headers,
|
||||
)
|
||||
else:
|
||||
@@ -827,10 +746,8 @@ async def get_file_content_by_id(
|
||||
############################
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_file_by_id(
|
||||
id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)
|
||||
):
|
||||
@router.delete('/{id}')
|
||||
async def delete_file_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
@@ -839,12 +756,7 @@ async def delete_file_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if (
|
||||
file.user_id == user.id
|
||||
or user.role == "admin"
|
||||
or has_access_to_file(id, "write", user, db=db)
|
||||
):
|
||||
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'write', user, db=db):
|
||||
# Clean up KB associations and embeddings before deleting
|
||||
knowledges = Knowledges.get_knowledges_by_file_id(id, db=db)
|
||||
for knowledge in knowledges:
|
||||
@@ -852,33 +764,29 @@ async def delete_file_by_id(
|
||||
Knowledges.remove_file_from_knowledge_by_id(knowledge.id, id, db=db)
|
||||
# Clean KB embeddings (same logic as /knowledge/{id}/file/remove)
|
||||
try:
|
||||
VECTOR_DB_CLIENT.delete(
|
||||
collection_name=knowledge.id, filter={"file_id": id}
|
||||
)
|
||||
VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'file_id': id})
|
||||
if file.hash:
|
||||
VECTOR_DB_CLIENT.delete(
|
||||
collection_name=knowledge.id, filter={"hash": file.hash}
|
||||
)
|
||||
VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'hash': file.hash})
|
||||
except Exception as e:
|
||||
log.debug(f"KB embedding cleanup for {knowledge.id}: {e}")
|
||||
log.debug(f'KB embedding cleanup for {knowledge.id}: {e}')
|
||||
|
||||
result = Files.delete_file_by_id(id, db=db)
|
||||
if result:
|
||||
try:
|
||||
Storage.delete_file(file.path)
|
||||
VECTOR_DB_CLIENT.delete(collection_name=f"file-{id}")
|
||||
VECTOR_DB_CLIENT.delete(collection_name=f'file-{id}')
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
log.error("Error deleting files")
|
||||
log.error('Error deleting files')
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT("Error deleting files"),
|
||||
detail=ERROR_MESSAGES.DEFAULT('Error deleting files'),
|
||||
)
|
||||
return {"message": "File deleted successfully"}
|
||||
return {'message': 'File deleted successfully'}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT("Error deleting file"),
|
||||
detail=ERROR_MESSAGES.DEFAULT('Error deleting file'),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
|
||||
Reference in New Issue
Block a user