image_upload format changed

This commit is contained in:
2025-09-25 05:53:41 +02:00
parent 406c968111
commit 8034c9ec11
3 changed files with 44 additions and 37 deletions

View File

@@ -4,6 +4,7 @@ import base64
from PIL import Image from PIL import Image
import io import io
from niceguiex.components import FileDrop, ImageDrop from niceguiex.components import FileDrop, ImageDrop
from niceguiex.components.file_drop import FileData, ImageData
@ui.page('/') @ui.page('/')
@@ -13,14 +14,12 @@ async def main_page():
# Example 1: Multiple files with content # Example 1: Multiple files with content
uploaded_files = ui.column().classes('w-full mt-4') uploaded_files = ui.column().classes('w-full mt-4')
def handle_multiple_files(files: List[Dict[str, Any]]): def handle_multiple_files(file: FileData):
with uploaded_files: with uploaded_files:
ui.label(f'Uploaded {len(files)} file(s):').classes('font-bold mb-2')
for file in files:
with ui.card().classes('p-2 mb-2'): with ui.card().classes('p-2 mb-2'):
ui.label(f"📄 {file['name']}").classes('font-medium') ui.label(f"📄 {file['name']}").classes('font-medium')
ui.label(f"Size: {file['size']:,} bytes").classes('text-sm text-gray-600') ui.label(f"Size: {file['size']:,} bytes").classes('text-sm text-gray-600')
ui.label(f"Type: {file['type']}").classes('text-sm text-gray-600') ui.label(f"Type: {file['file_type']}").classes('text-sm text-gray-600')
ui.label('Multiple Files (returns list)').classes('text-h6 mb-2') ui.label('Multiple Files (returns list)').classes('text-h6 mb-2')
FileDrop( FileDrop(
@@ -73,16 +72,16 @@ async def main_page():
# Example 4: Image drop with preview # Example 4: Image drop with preview
image_preview = ui.column().classes('w-full mt-4') image_preview = ui.column().classes('w-full mt-4')
def handle_image(img: Image.Image): def handle_image(img: ImageData):
print(f"Image uploaded: {img.format}, Size: {img.size}") print(f"Image uploaded: {img['image'].format}, Size: {img['size']}")
image_preview.clear() image_preview.clear()
with image_preview: with image_preview:
ui.label(f"Image: {img.format} - {img.size[0]}x{img.size[1]}px").classes('mb-2') ui.label(f"Image: {img['image'].format} - {img['image'].size[0]}x{img['image'].size[1]}px").classes('mb-2')
# Convert PIL image back to base64 for display # Convert PIL image back to base64 for display
buffered = io.BytesIO() buffered = io.BytesIO()
img.save(buffered, format=img.format or 'PNG') img['image'].save(buffered, format=img['image'].format or 'PNG')
img_data = base64.b64encode(buffered.getvalue()).decode() img_data = base64.b64encode(buffered.getvalue()).decode()
ui.html(f'<img src="data:image/{img.format.lower() if img.format else "png"};base64,{img_data}" class="max-w-full rounded">') ui.html(f'<img src="data:image/{img['image'].format.lower() if img['image'].format else "png"};base64,{img_data}" class="max-w-full rounded">')
ui.label('Image Drop (returns PIL Image)').classes('text-h6 mb-2') ui.label('Image Drop (returns PIL Image)').classes('text-h6 mb-2')
ImageDrop( ImageDrop(

View File

@@ -1,5 +1,5 @@
from .auto_scroll_area import AutoScrollArea from .auto_scroll_area import AutoScrollArea
from .chat_input import ChatInput from .chat_input import ChatInput
from .file_drop import FileDrop, ImageDrop from .file_drop import FileDrop, ImageDrop, FileData, ImageData
__all__ = ['AutoScrollArea', 'ChatInput', 'FileDrop', 'ImageDrop'] __all__ = ['AutoScrollArea', 'ChatInput', 'FileDrop', 'ImageDrop', 'FileData', 'ImageData']

View File

@@ -1,11 +1,26 @@
from nicegui import ui from nicegui import ui
from typing import Optional, Callable from typing import Optional, Callable, TypedDict, Any
from pathlib import Path from pathlib import Path
import tempfile import tempfile
from PIL import Image from PIL import Image
import io import io
class FileData(TypedDict):
name: str
size: int
file_type: str
content: Optional[Any]
path: Optional[Path]
class ImageData(TypedDict):
name: str
size: int
file_type: str
image: Image.Image
class FileDrop(ui.element): class FileDrop(ui.element):
def __init__(self, def __init__(self,
on_upload: Optional[Callable] = None, on_upload: Optional[Callable] = None,
@@ -100,24 +115,13 @@ class FileDrop(ui.element):
# Process single file upload # Process single file upload
if hasattr(e, 'content'): if hasattr(e, 'content'):
print('content')
file_data = self._process_file(e) file_data = self._process_file(e)
if file_data: if file_data:
# For single file mode, return the dict directly # For single file mode, return the dict directly
if not self.multiple:
self.on_upload_callback(file_data) self.on_upload_callback(file_data)
else:
self.on_upload_callback([file_data])
# Handle multiple files
elif hasattr(e, 'files'):
files = []
for file_info in e.files:
file_data = self._process_file(file_info)
if file_data:
files.append(file_data)
if files:
self.on_upload_callback(files)
def _process_file(self, file_obj): def _process_file(self, file_obj) -> FileData | None:
"""Process a single file object""" """Process a single file object"""
if not hasattr(file_obj, 'content'): if not hasattr(file_obj, 'content'):
return None return None
@@ -130,11 +134,11 @@ class FileDrop(ui.element):
ui.notify(f"File too large. Max size is {self.max_size}MB", type='negative') ui.notify(f"File too large. Max size is {self.max_size}MB", type='negative')
return None return None
file_data = { file_data: FileData = {'name': file_obj.name if hasattr(file_obj, 'name') else 'unknown',
'name': file_obj.name if hasattr(file_obj, 'name') else 'unknown',
'size': size, 'size': size,
'type': file_obj.type if hasattr(file_obj, 'type') else 'application/octet-stream', 'file_type': file_obj.type if hasattr(file_obj, 'type') else 'application/octet-stream',
} 'content': None,
'path': None}
# Return content or temp file path based on settings # Return content or temp file path based on settings
if self.return_content: if self.return_content:
@@ -171,7 +175,7 @@ class ImageDrop(FileDrop):
*args, **kwargs *args, **kwargs
) )
def _handle_image_upload(self, data): def _handle_image_upload(self, data: FileData):
"""Convert file data to PIL Images before calling user callback""" """Convert file data to PIL Images before calling user callback"""
if self._user_callback: if self._user_callback:
if isinstance(data, list) and len(data) == 1: if isinstance(data, list) and len(data) == 1:
@@ -186,5 +190,9 @@ class ImageDrop(FileDrop):
self._user_callback(images) self._user_callback(images)
else: else:
# Single image - convert to PIL Image # Single image - convert to PIL Image
img = Image.open(io.BytesIO(data['content'])) image_data: ImageData = {'name': data['name'],
self._user_callback(img) 'size': data['size'],
'file_type': data['file_type'],
'image': Image.open(io.BytesIO(data['content']))}
# img = Image.open(io.BytesIO(data['content']))
self._user_callback(image_data)