"""Helpers for attaching images to FastGPT chat messages. FastGPT is OpenAI-compatible and accepts two ways to send an image inside a message's ``content`` list as an ``image_url`` part: 1. **Inline base64** — a ``data:;base64,`` data URL. Nothing is uploaded; the image travels inside the request body. Cheapest to send and requires no ``appId``/``chatId``. 2. **Uploaded URL** — upload the bytes first (see :meth:`ChatClient.upload_chat_image`) and reference the returned URL. Keeps the request body small and lets the image be reused/previewed later. These helpers build the ``image_url`` content parts for either method and let you convert an inline data URL back into raw bytes (used when uploading). """ from __future__ import annotations import base64 import binascii import mimetypes from pathlib import Path from typing import Any __all__ = [ "encode_image_data_url", "is_data_url", "decode_data_url", "image_url_part", "image_part_from_bytes", "image_part_from_path", ] _DATA_URL_PREFIX = "data:" def encode_image_data_url(data: bytes, mime_type: str = "image/jpeg") -> str: """Return a ``data:;base64,`` URL for raw image bytes.""" payload = base64.b64encode(data).decode("ascii") return f"{_DATA_URL_PREFIX}{mime_type};base64,{payload}" def is_data_url(url: Any) -> bool: """True if ``url`` is an inline base64 data URL (``data:...``).""" return isinstance(url, str) and url.startswith(_DATA_URL_PREFIX) def decode_data_url(data_url: str) -> tuple[str, bytes]: """Split a base64 data URL into ``(mime_type, raw_bytes)``. Raises: ValueError: if ``data_url`` is not a data URL or its base64 payload is malformed. """ if not is_data_url(data_url): raise ValueError("Not a base64 data URL") header, _, payload = data_url.partition(",") mime_type = header[len(_DATA_URL_PREFIX):].split(";", 1)[0].strip() or "image/jpeg" try: raw = base64.b64decode(payload, validate=True) except (binascii.Error, ValueError) as exc: raise ValueError(f"Invalid base64 data URL payload: {exc}") from exc return mime_type, raw def image_url_part(url: str) -> dict[str, Any]: """Build an ``image_url`` content part from a URL or inline data URL.""" return {"type": "image_url", "image_url": {"url": url}} def image_part_from_bytes(data: bytes, mime_type: str = "image/jpeg") -> dict[str, Any]: """Build an inline base64 ``image_url`` content part from raw bytes.""" return image_url_part(encode_image_data_url(data, mime_type)) def image_part_from_path(file_path: str | Path) -> dict[str, Any]: """Read a local image file and build an inline base64 ``image_url`` part.""" path = Path(file_path) mime_type = mimetypes.guess_type(path.name)[0] or "image/jpeg" return image_part_from_bytes(path.read_bytes(), mime_type)