feat: Add image upload support for Spec Creation chat

Add the ability to attach images (JPEG, PNG) in the Spec Creation chat
interface for Claude to analyze during app specification creation.

Frontend changes:
- Add ImageAttachment interface to types.ts with id, filename, mimeType,
  base64Data, previewUrl, and size fields
- Update ChatMessage interface with optional attachments field
- Update useSpecChat hook to accept and send attachments via WebSocket
- Add file input, drag-drop support, and preview thumbnails to
  SpecCreationChat component with validation (5 MB max, JPEG/PNG only)
- Update ChatMessage component to render image attachments with
  click-to-enlarge functionality

Backend changes:
- Add ImageAttachment Pydantic schema with base64 validation
- Update spec_creation.py WebSocket handler to parse and validate
  image attachments from client messages
- Update spec_chat_session.py to format multimodal content blocks
  for Claude API using async generator pattern

Features:
- Drag-and-drop or click paperclip button to attach images
- Preview thumbnails with remove button before sending
- File type validation (image/jpeg, image/png)
- File size validation (5 MB maximum)
- Images display in chat history
- Click images to view full size
- Cross-platform compatible (Windows, macOS, Linux)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-01-02 10:12:04 +02:00
parent 05607b310a
commit b628aa7051
7 changed files with 335 additions and 52 deletions

View File

@@ -13,8 +13,9 @@ from pathlib import Path
from typing import Any, Optional
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, HTTPException
from pydantic import BaseModel
from pydantic import BaseModel, ValidationError
from ..schemas import ImageAttachment
from ..services.spec_chat_session import (
SpecChatSession,
get_session,
@@ -191,7 +192,24 @@ async def spec_chat_websocket(websocket: WebSocket, project_name: str):
continue
user_content = message.get("content", "").strip()
if not user_content:
# Parse attachments if present
attachments: list[ImageAttachment] = []
raw_attachments = message.get("attachments", [])
if raw_attachments:
try:
for raw_att in raw_attachments:
attachments.append(ImageAttachment(**raw_att))
except (ValidationError, Exception) as e:
logger.warning(f"Invalid attachment data: {e}")
await websocket.send_json({
"type": "error",
"content": f"Invalid attachment: {str(e)}"
})
continue
# Allow empty content if attachments are present
if not user_content and not attachments:
await websocket.send_json({
"type": "error",
"content": "Empty message"
@@ -202,8 +220,8 @@ async def spec_chat_websocket(websocket: WebSocket, project_name: str):
spec_complete_received = False
spec_path = None
# Stream Claude's response
async for chunk in session.send_message(user_content):
# Stream Claude's response (with attachments if present)
async for chunk in session.send_message(user_content, attachments if attachments else None):
# Track spec_complete but don't send complete yet
if chunk.get("type") == "spec_complete":
spec_complete_received = True