Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion backend/hf_api_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ async def _make_request(client, url, payload):
logger.error(f"HF API Request Exception: {e}")
return []

def _prepare_image_bytes(image: Union[Image.Image, bytes]) -> bytes:
def _prepare_image_bytes(image: Union[Image.Image, bytes, io.BytesIO]) -> bytes:
if isinstance(image, bytes):
return image
if isinstance(image, io.BytesIO):
return image.getvalue()
img_byte_arr = io.BytesIO()
fmt = image.format if image.format else 'JPEG'
image.save(img_byte_arr, format=fmt)
Expand Down
2 changes: 1 addition & 1 deletion backend/routers/issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ async def create_issue(
image_path = os.path.join(upload_dir, filename)

# Process image (validate, resize, strip EXIF)
processed_image = await process_uploaded_image(image)
_, processed_image = await process_uploaded_image(image)

# Save processed image to disk
await run_in_threadpool(save_processed_image, processed_image, image_path)
Comment on lines +73 to 76
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local name processed_image is now an io.BytesIO (image bytes), not a PIL image. Renaming it to something like processed_image_io / processed_image_bytes would make the code clearer and reduce the chance of accidentally treating it as an Image.Image later.

Suggested change
_, processed_image = await process_uploaded_image(image)
# Save processed image to disk
await run_in_threadpool(save_processed_image, processed_image, image_path)
_, processed_image_bytes = await process_uploaded_image(image)
# Save processed image to disk
await run_in_threadpool(save_processed_image, processed_image_bytes, image_path)

Copilot uses AI. Check for mistakes.
Expand Down
7 changes: 7 additions & 0 deletions backend/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ class ActionPlan(BaseModel):
class ChatRequest(BaseModel):
query: str = Field(..., min_length=1, max_length=1000, description="Chat query text")

@field_validator('query')
@classmethod
def validate_query(cls, v):
if not v.strip():
raise ValueError('Query cannot be empty or whitespace only')
return v.strip()

class ChatResponse(BaseModel):
response: str

Expand Down
8 changes: 4 additions & 4 deletions backend/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,10 @@ async def validate_uploaded_file(file: UploadFile) -> Optional[Image.Image]:
"""
return await run_in_threadpool(_validate_uploaded_file_sync, file)

def process_uploaded_image_sync(file: UploadFile) -> io.BytesIO:
def process_uploaded_image_sync(file: UploadFile) -> tuple[Image.Image, io.BytesIO]:
"""
Synchronously validate, resize, and strip EXIF from uploaded image.
Returns the processed image data as BytesIO.
Returns the processed PIL image and image data as BytesIO.
"""
# Check file size
file.file.seek(0, 2)
Expand Down Expand Up @@ -187,7 +187,7 @@ def process_uploaded_image_sync(file: UploadFile) -> io.BytesIO:
img_no_exif.save(output, format=fmt, quality=85)
output.seek(0)

return output
return img_no_exif, output

except Exception as pil_error:
logger.error(f"PIL processing failed: {pil_error}")
Expand All @@ -202,7 +202,7 @@ def process_uploaded_image_sync(file: UploadFile) -> io.BytesIO:
logger.error(f"Error processing file: {e}")
raise HTTPException(status_code=400, detail="Unable to process file.")

async def process_uploaded_image(file: UploadFile) -> io.BytesIO:
async def process_uploaded_image(file: UploadFile) -> tuple[Image.Image, io.BytesIO]:
return await run_in_threadpool(process_uploaded_image_sync, file)
Comment on lines 141 to 206
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

process_uploaded_image/process_uploaded_image_sync now return a tuple, but there are no unit tests asserting the new return contract (PIL image + BytesIO) or that the BytesIO is rewound and usable for downstream consumers. Adding a focused test here would prevent regressions like unpacking errors or non-seeked buffers.

Copilot uses AI. Check for mistakes.

def save_processed_image(file_obj: io.BytesIO, path: str):
Expand Down
Loading