Add image upload and vision analysis to Ask Simba chat
Users can now attach images in the web chat for Simba to analyze using Ollama's gemma3 vision model. Images are stored in Garage (S3-compatible) and displayed in chat history. Also fixes aerich migration config by extracting TORTOISE_CONFIG into a standalone config/db.py module, removing the stale aerich_config.py, and adding missing MODELS_STATE to migration 3. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
62
utils/image_upload.py
Normal file
62
utils/image_upload.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import io
|
||||
import logging
|
||||
|
||||
from PIL import Image
|
||||
from pillow_heif import register_heif_opener
|
||||
|
||||
register_heif_opener()
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
ALLOWED_TYPES = {"image/jpeg", "image/png", "image/webp", "image/heic", "image/heif"}
|
||||
MAX_DIMENSION = 1920
|
||||
|
||||
|
||||
class ImageValidationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def process_image(file_bytes: bytes, content_type: str) -> tuple[bytes, str]:
|
||||
"""Validate, resize, and strip EXIF from an uploaded image.
|
||||
|
||||
Returns processed bytes and the output content type (always image/jpeg or image/png or image/webp).
|
||||
"""
|
||||
if content_type not in ALLOWED_TYPES:
|
||||
raise ImageValidationError(
|
||||
f"Unsupported image type: {content_type}. "
|
||||
f"Allowed: JPEG, PNG, WebP, HEIC"
|
||||
)
|
||||
|
||||
img = Image.open(io.BytesIO(file_bytes))
|
||||
|
||||
# Resize if too large
|
||||
width, height = img.size
|
||||
if max(width, height) > MAX_DIMENSION:
|
||||
ratio = MAX_DIMENSION / max(width, height)
|
||||
new_size = (int(width * ratio), int(height * ratio))
|
||||
img = img.resize(new_size, Image.LANCZOS)
|
||||
logging.info(
|
||||
f"Resized image from {width}x{height} to {new_size[0]}x{new_size[1]}"
|
||||
)
|
||||
|
||||
# Strip EXIF by copying pixel data to a new image
|
||||
clean_img = Image.new(img.mode, img.size)
|
||||
clean_img.putdata(list(img.getdata()))
|
||||
|
||||
# Convert HEIC/HEIF to JPEG; otherwise keep original format
|
||||
if content_type in {"image/heic", "image/heif"}:
|
||||
output_format = "JPEG"
|
||||
output_content_type = "image/jpeg"
|
||||
elif content_type == "image/png":
|
||||
output_format = "PNG"
|
||||
output_content_type = "image/png"
|
||||
elif content_type == "image/webp":
|
||||
output_format = "WEBP"
|
||||
output_content_type = "image/webp"
|
||||
else:
|
||||
output_format = "JPEG"
|
||||
output_content_type = "image/jpeg"
|
||||
|
||||
buf = io.BytesIO()
|
||||
clean_img.save(buf, format=output_format, quality=85)
|
||||
return buf.getvalue(), output_content_type
|
||||
Reference in New Issue
Block a user