Spaces:
Running
Running
| from __future__ import annotations | |
| from typing import Any | |
| from nicegui import events, ui | |
| from velai import image_utils | |
| from velai.data_types import ImageType | |
| from velai.dataflow.enums import NodeKind, PortDirection | |
| from velai.dataflow.nodes import NodeType | |
| from velai.dataflow.ports import PortSchema | |
| from velai.nodes.actions.node_action_decorator import as_action_name, node_action | |
| from velai.nodes.actions.node_action_models import AsyncNodeActionResultIterator, NodeActionArguments, NodeActionResult | |
| from velai.nodes.base_node import BaseNode, BaseNodeData | |
| from velai.nodes.base_node_renderable import T_BASE_NODE, BaseNodeRenderable | |
| from velai.storage import storage_endpoint | |
| ImageDataNodeType = NodeType( | |
| kind=NodeKind.IMAGE_DATA, | |
| display_name="Image Upload", | |
| inputs=[], | |
| outputs=[PortSchema(name="image", dtype=ImageType, direction=PortDirection.OUTPUT, tooltip="Image")], | |
| ) | |
| class ImageDataNode(BaseNode[BaseNodeData]): | |
| """Node that exposes a constant image value via its output port. | |
| The text is stored on the "text" output port and edited through the UI. | |
| """ | |
| class ImageDataNodeRenderable(BaseNodeRenderable[ImageDataNode]): | |
| def get_header_buttons(self, node: T_BASE_NODE) -> list[dict[str, Any]]: | |
| return [ | |
| *super().get_header_buttons(node), | |
| { | |
| "name": "upload_python", | |
| "icon": "upload", | |
| "tooltip": "Upload image", | |
| "action": as_action_name(self._on_upload_image), | |
| "disableWhileProcessing": True, | |
| }, | |
| ] | |
| def get_fields(self, node: T_BASE_NODE) -> list[dict[str, Any]]: | |
| return [ | |
| *super().get_fields(node), | |
| { | |
| "name": "image", | |
| "kind": "image", | |
| "label": "Image", | |
| "placeholder": "No image", | |
| }, | |
| ] | |
| async def _on_upload_image(self, args: NodeActionArguments) -> AsyncNodeActionResultIterator: | |
| # create the dialog in the main page content context | |
| with ui.context.client.content: | |
| with ui.dialog() as dialog: | |
| with ui.card(): | |
| ui.label("Upload an image") | |
| async def handle_upload(e: events.UploadEventArguments) -> None: | |
| file = e.file | |
| data = await file.read() | |
| # resize image file to max side length, keeping aspect ratio | |
| try: | |
| image_data = await image_utils.prepare_image_bytes_for_storage_async(data) | |
| except ValueError as ex: | |
| ui.notification(f"Image upload did not work {ex}", type="negative") | |
| dialog.submit(False) | |
| return | |
| # download and store mesh | |
| image_url = await storage_endpoint.store_data( | |
| image_data, original_name=file.name, mime_type=e.file.content_type | |
| ) | |
| # update image url | |
| args.node.outputs["image"].value = image_url | |
| dialog.submit(True) | |
| # upload definition | |
| ( | |
| ui.upload( | |
| label="Choose an image", | |
| on_upload=handle_upload, | |
| max_files=1, | |
| multiple=False, | |
| max_file_size=1024 * 1024 * 1024 * 5, # max 5MB | |
| auto_upload=True, | |
| ).props("accept=.jpeg,.jpg,.gif,.png,.tiff,.webp") | |
| ) | |
| ui.button("Cancel", on_click=dialog.close) | |
| # open and run dialog | |
| await dialog | |
| yield NodeActionResult.update_node() | |