Accept OpenAI multipart image edits
This commit is contained in:
+54
-10
@@ -2223,7 +2223,25 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self._image_request(data, [])
|
||||
|
||||
def _image_edit(self) -> None:
|
||||
"""Edit with local image bytes supplied by the private Hermes plugin."""
|
||||
"""Edit with OpenAI multipart uploads or legacy JSON/base64 input."""
|
||||
content_type = self.headers.get("Content-Type", "")
|
||||
if "multipart/form-data" in content_type:
|
||||
try:
|
||||
body = self._read_body()
|
||||
files, data = self._parse_multipart_parts(body, content_type)
|
||||
except ValueError as exc:
|
||||
self._send_error(400, str(exc),
|
||||
"invalid_request_error", "invalid_multipart")
|
||||
return
|
||||
images = [payload for name, _filename, payload in files
|
||||
if name in {"image", "image[]"}]
|
||||
if not images:
|
||||
self._send_error(400, "Referenzbild fehlt",
|
||||
"invalid_request_error", "missing_image")
|
||||
return
|
||||
self._image_edit_bytes(data, images)
|
||||
return
|
||||
|
||||
data = self._read_image_request()
|
||||
if data is None:
|
||||
return
|
||||
@@ -2249,7 +2267,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
"invalid_request_error", "too_many_images")
|
||||
return
|
||||
|
||||
source_files: list[str] = []
|
||||
decoded: list[bytes] = []
|
||||
try:
|
||||
for item in encoded:
|
||||
if item.startswith("data:"):
|
||||
@@ -2260,6 +2278,25 @@ class Handler(BaseHTTPRequestHandler):
|
||||
raw = base64.b64decode(item, validate=True)
|
||||
except Exception as exc:
|
||||
raise ValueError("ungültige Base64-Bilddaten") from exc
|
||||
decoded.append(raw)
|
||||
self._image_edit_bytes(data, decoded)
|
||||
except ValueError as exc:
|
||||
self._send_error(400, str(exc),
|
||||
"invalid_request_error", "invalid_image")
|
||||
|
||||
def _image_edit_bytes(self, data: dict, images: list[bytes]) -> None:
|
||||
if len(images) > 4:
|
||||
self._send_error(400, "höchstens vier Referenzbilder erlaubt",
|
||||
"invalid_request_error", "too_many_images")
|
||||
return
|
||||
if not images:
|
||||
self._send_error(400, "Referenzbild fehlt",
|
||||
"invalid_request_error", "missing_image")
|
||||
return
|
||||
|
||||
source_files: list[str] = []
|
||||
try:
|
||||
for raw in images:
|
||||
if not raw or len(raw) > CHAT_IMAGE_MAX_BYTES:
|
||||
raise ValueError(
|
||||
f"Referenzbild muss 1..{CHAT_IMAGE_MAX_BYTES} Bytes groß sein")
|
||||
@@ -2652,9 +2689,9 @@ class Handler(BaseHTTPRequestHandler):
|
||||
|
||||
# ---------- STT (Spracherkennung) ----------
|
||||
|
||||
def _parse_multipart(self, data: bytes, content_type: str
|
||||
) -> tuple[bytes, str, dict]:
|
||||
"""Parst multipart/form-data. Liefert (file_data, filename, fields).
|
||||
def _parse_multipart_parts(self, data: bytes, content_type: str
|
||||
) -> tuple[list[tuple[str, str, bytes]], dict]:
|
||||
"""Parst alle Datei- und Textteile eines multipart/form-data-Body.
|
||||
|
||||
Nutzt email.parser.BytesParser (Standardbibliothek) für robustes
|
||||
MIME-Parsing. Handhabt quoted und unquoted Boundaries, beliebige
|
||||
@@ -2667,9 +2704,8 @@ class Handler(BaseHTTPRequestHandler):
|
||||
if not msg.is_multipart():
|
||||
raise ValueError("Kein multipart/form-data")
|
||||
|
||||
file_data = b""
|
||||
filename = ""
|
||||
fields = {}
|
||||
files: list[tuple[str, str, bytes]] = []
|
||||
fields: dict[str, str] = {}
|
||||
|
||||
for part in msg.get_payload():
|
||||
disposition = part.get("Content-Disposition", "")
|
||||
@@ -2690,12 +2726,20 @@ class Handler(BaseHTTPRequestHandler):
|
||||
|
||||
if part_filename is not None:
|
||||
# Dateifeld (binär, nicht dekodieren)
|
||||
file_data = payload
|
||||
filename = part_filename or ""
|
||||
files.append((name, part_filename or "", payload))
|
||||
else:
|
||||
# Textfeld
|
||||
fields[name] = payload.decode("utf-8", errors="replace")
|
||||
|
||||
return files, fields
|
||||
|
||||
def _parse_multipart(self, data: bytes, content_type: str
|
||||
) -> tuple[bytes, str, dict]:
|
||||
"""Kompatibler Einzeldatei-Wrapper für den STT-Pfad."""
|
||||
files, fields = self._parse_multipart_parts(data, content_type)
|
||||
if not files:
|
||||
return b"", "", fields
|
||||
_name, filename, file_data = files[-1]
|
||||
return file_data, filename, fields
|
||||
|
||||
def _transcribe(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user