image_upload format changed
This commit is contained in:
@@ -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')
|
with ui.card().classes('p-2 mb-2'):
|
||||||
for file in files:
|
ui.label(f"📄 {file['name']}").classes('font-medium')
|
||||||
with ui.card().classes('p-2 mb-2'):
|
ui.label(f"Size: {file['size']:,} bytes").classes('text-sm text-gray-600')
|
||||||
ui.label(f"📄 {file['name']}").classes('font-medium')
|
ui.label(f"Type: {file['file_type']}").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('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(
|
||||||
|
|||||||
@@ -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']
|
||||||
|
|||||||
@@ -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,
|
'file_type': file_obj.type if hasattr(file_obj, 'type') else 'application/octet-stream',
|
||||||
'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)
|
||||||
|
|||||||
Reference in New Issue
Block a user