diff --git a/docs/superpowers/plans/2026-03-24-imagecreator-implementation.md b/docs/superpowers/plans/2026-03-24-imagecreator-implementation.md new file mode 100644 index 0000000..36e701b --- /dev/null +++ b/docs/superpowers/plans/2026-03-24-imagecreator-implementation.md @@ -0,0 +1,2522 @@ +# ImageCreator Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a full-stack image generation website with user authentication, prompt optimization, and T2I/I2I generation capabilities. + +**Architecture:** FastAPI backend with PostgreSQL, Vue 3 frontend with Naive UI, Docker Compose deployment. Backend handles all image generation logic via adapter pattern (referencing prompt-optimizer architecture). + +**Tech Stack:** Python FastAPI, SQLAlchemy, PostgreSQL, JWT, Vue 3, TypeScript, Naive UI, TailwindCSS, Vite, Docker + +--- + +## File Structure + +``` +/opt/imagecreator/imagecreator/ +├── backend/ +│ ├── app/ +│ │ ├── __init__.py +│ │ ├── main.py # FastAPI app entry +│ │ ├── config.py # Settings from env +│ │ ├── database.py # SQLAlchemy engine/session +│ │ ├── api/ +│ │ │ ├── __init__.py +│ │ │ ├── deps.py # Dependency injection (get_db, get_current_user) +│ │ │ ├── auth.py # /api/auth routes +│ │ │ ├── users.py # /api/users routes +│ │ │ ├── images.py # /api/images routes +│ │ │ └── history.py # /api/history routes +│ │ ├── core/ +│ │ │ ├── __init__.py +│ │ │ └── security.py # JWT, password hashing +│ │ ├── models/ +│ │ │ ├── __init__.py +│ │ │ ├── user.py # User SQLAlchemy model +│ │ │ └── history.py # GenerationHistory model +│ │ ├── schemas/ +│ │ │ ├── __init__.py +│ │ │ ├── auth.py # Login/Register schemas +│ │ │ ├── user.py # User schemas +│ │ │ ├── image.py # Image generate/optimize schemas +│ │ │ └── history.py # History schemas +│ │ └── services/ +│ │ ├── __init__.py +│ │ ├── auth.py # AuthService (register, login) +│ │ ├── image.py # ImageService (generate, optimize) +│ │ └── llm.py # LLM client for prompt optimization +│ ├── tests/ +│ │ ├── __init__.py +│ │ ├── test_auth.py +│ │ ├── test_images.py +│ │ └── test_history.py +│ ├── requirements.txt +│ └── Dockerfile +├── frontend/ +│ ├── src/ +│ │ ├── api/ +│ │ │ ├── index.ts # Axios instance +│ │ │ ├── auth.ts # Auth API calls +│ │ │ ├── images.ts # Image generation API calls +│ │ │ └── history.ts # History API calls +│ │ ├── components/ +│ │ │ ├── ImageGenerator.vue +│ │ │ ├── PromptOptimizer.vue +│ │ │ ├── ImageHistory.vue +│ │ │ ├── ImagePreview.vue +│ │ │ └── ... +│ │ ├── views/ +│ │ │ ├── Login.vue +│ │ │ ├── Register.vue +│ │ │ ├── Home.vue +│ │ │ └── History.vue +│ │ ├── stores/ +│ │ │ └── auth.ts # Pinia auth store +│ │ ├── router/ +│ │ │ └── index.ts +│ │ ├── App.vue +│ │ └── main.ts +│ ├── package.json +│ ├── vite.config.ts +│ ├── tailwind.config.js +│ ├── index.html +│ └── Dockerfile +├── docker-compose.yml +└── README.md +``` + +--- + +## Phase 1: Project Structure & Backend Foundation + +### Task 1: Create project directory structure + +**Files:** +- Create: `/opt/imagecreator/imagecreator/backend/app/__init__.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/main.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/config.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/database.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/api/__init__.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/api/deps.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/core/__init__.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/core/security.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/models/__init__.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/models/user.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/models/history.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/schemas/__init__.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/schemas/auth.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/schemas/user.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/schemas/image.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/schemas/history.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/services/__init__.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/services/auth.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/services/image.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/services/llm.py` +- Create: `/opt/imagecreator/imagecreator/backend/requirements.txt` +- Create: `/opt/imagecreator/imagecreator/backend/Dockerfile` +- Create: `/opt/imagecreator/imagecreator/frontend/` (Vue project structure) + +- [ ] **Step 1: Create backend directory structure** + +Run: `mkdir -p /opt/imagecreator/imagecreator/backend/app/{api,core,models,schemas,services} /opt/imagecreator/imagecreator/backend/tests` + +- [ ] **Step 2: Create backend requirements.txt** + +```txt +fastapi==0.109.0 +uvicorn[standard]==0.27.0 +sqlalchemy==2.0.25 +psycopg2-binary==2.9.9 +python-jose[cryptography]==3.3.0 +passlib[bcrypt]==1.7.4 +python-multipart==0.0.6 +pydantic==2.5.3 +pydantic-settings==2.1.0 +google-genai==0.8.0 +openai==1.12.0 +aiofiles==23.2.1 +httpx==0.26.0 +pytest==8.0.0 +pytest-asyncio==0.23.4 +``` + +- [ ] **Step 3: Create backend/app/__init__.py and main.py** + +```python +# app/__init__.py +``` + +```python +# app/main.py +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from app.api import auth, users, images, history +from app.config import settings +from app.database import engine +from app.models import user, history as history_model + +app = FastAPI(title="ImageCreator API", version="1.0.0") + +app.add_middleware( + CORSMiddleware, + allow_origins=settings.CORS_ORIGINS, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.include_router(auth.router, prefix="/api/auth", tags=["auth"]) +app.include_router(users.router, prefix="/api/users", tags=["users"]) +app.include_router(images.router, prefix="/api/images", tags=["images"]) +app.include_router(history.router, prefix="/api/history", tags=["history"]) + +@app.get("/health") +def health_check(): + return {"status": "ok"} +``` + +- [ ] **Step 4: Create app/config.py** + +```python +# app/config.py +from pydantic_settings import BaseSettings +from functools import lru_cache + +class Settings(BaseSettings): + # Database + DATABASE_URL: str = "postgresql://user:pass@localhost:5432/imagecreator" + + # JWT + SECRET_KEY: str = "your-secret-key-change-in-production" + ALGORITHM: str = "HS256" + ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 + REFRESH_TOKEN_EXPIRE_DAYS: int = 7 + + # CORS + CORS_ORIGINS: list[str] = ["http://localhost:3000"] + + # LLM API Keys + GEMINI_API_KEY: str = "" + OPENAI_API_KEY: str = "" + + # Image storage + IMAGE_STORAGE_PATH: str = "/app/uploads" + + class Config: + env_file = ".env" + +@lru_cache() +def get_settings(): + return Settings() + +settings = get_settings() +``` + +- [ ] **Step 5: Create app/database.py** + +```python +# app/database.py +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base + +from app.config import settings + +engine = create_engine(settings.DATABASE_URL, pool_pre_ping=True) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() +``` + +- [ ] **Step 6: Commit** + +```bash +git add -A && git commit -m "feat: create backend project structure" +``` + +--- + +### Task 2: Implement database models + +**Files:** +- Modify: `/opt/imagecreator/imagecreator/backend/app/models/user.py` +- Modify: `/opt/imagecreator/imagecreator/backend/app/models/history.py` +- Create: `/opt/imagecreator/imagecreator/backend/tests/test_models.py` + +- [ ] **Step 1: Create User model** + +```python +# app/models/user.py +import uuid +from datetime import datetime +from sqlalchemy import Column, String, DateTime +from sqlalchemy.dialects.postgresql import UUID + +from app.database import Base + +class User(Base): + __tablename__ = "users" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + username = Column(String(32), unique=True, nullable=False, index=True) + email = Column(String(255), unique=True, nullable=False, index=True) + hashed_password = Column(String(255), nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) +``` + +```python +# app/models/history.py +import uuid +from datetime import datetime +from sqlalchemy import Column, String, Text, DateTime, ForeignKey, Enum as SQLEnum +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship +import enum + +from app.database import Base + +class GenerationMode(str, enum.Enum): + TEXT2IMAGE = "text2image" + IMAGE2IMAGE = "image2image" + +class GenerationHistory(Base): + __tablename__ = "generation_history" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False) + original_prompt = Column(Text, nullable=False) + optimized_prompt = Column(Text, nullable=False) + mode = Column(SQLEnum(GenerationMode), nullable=False) + model = Column(String(32), nullable=False) + image_url = Column(String(512), nullable=False) + input_image_url = Column(String(512), nullable=True) # For I2I + created_at = Column(DateTime, default=datetime.utcnow) + + user = relationship("User", backref="generation_history") +``` + +- [ ] **Step 2: Create test for models** + +```python +# tests/test_models.py +import pytest +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from app.database import Base +from app.models.user import User +from app.models.history import GenerationHistory, GenerationMode + +engine = create_engine("sqlite:///:memory:") +TestSessionLocal = sessionmaker(bind=engine) + +def test_user_model(): + Base.metadata.create_all(bind=engine) + db = TestSessionLocal() + + user = User(username="testuser", email="test@example.com", hashed_password="hashed") + db.add(user) + db.commit() + + assert user.id is not None + assert user.username == "testuser" + assert user.email == "test@example.com" + + db.close() + +def test_generation_history_model(): + Base.metadata.create_all(bind=engine) + db = TestSessionLocal() + + user = User(username="testuser2", email="test2@example.com", hashed_password="hashed") + db.add(user) + db.commit() + + history = GenerationHistory( + user_id=user.id, + original_prompt="a cat", + optimized_prompt="a fluffy orange cat sitting on a windowsill", + mode=GenerationMode.TEXT2IMAGE, + model="gemini-2.5-flash-image", + image_url="/images/cat.png" + ) + db.add(history) + db.commit() + + assert history.id is not None + assert history.user_id == user.id + assert history.mode == GenerationMode.TEXT2IMAGE + + db.close() +``` + +- [ ] **Step 3: Run tests** + +Run: `cd /opt/imagecreator/imagecreator/backend && pytest tests/test_models.py -v` +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add -A && git commit -m "feat: add database models (User, GenerationHistory)" +``` + +--- + +### Task 3: Implement security module (JWT & password) + +**Files:** +- Modify: `/opt/imagecreator/imagecreator/backend/app/core/security.py` +- Create: `/opt/imagecreator/imagecreator/backend/tests/test_security.py` + +- [ ] **Step 1: Write failing test** + +```python +# tests/test_security.py +import pytest +from app.core.security import verify_password, hash_password, create_access_token, decode_access_token + +def test_password_hash_and_verify(): + password = "testpassword123" + hashed = hash_password(password) + + assert hashed != password + assert verify_password(password, hashed) is True + assert verify_password("wrongpassword", hashed) is False + +def test_access_token_creation_and_decode(): + token = create_access_token({"sub": "testuser"}) + + assert token is not None + payload = decode_access_token(token) + + assert payload is not None + assert payload.sub == "testuser" +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd /opt/imagecreator/imagecreator/backend && pytest tests/test_security.py -v` +Expected: FAIL - verify_password not defined + +- [ ] **Step 3: Write minimal implementation** + +```python +# app/core/security.py +from datetime import datetime, timedelta +from typing import Optional +from jose import JWTError, jwt +from passlib.context import CryptContext + +from app.config import settings + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +def hash_password(password: str) -> str: + return pwd_context.hash(password) + +def verify_password(plain_password: str, hashed_password: str) -> bool: + return pwd_context.verify(plain_password, hashed_password) + +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) + return encoded_jwt + +def decode_access_token(token: str) -> Optional[dict]: + try: + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) + return payload + except JWTError: + return None +``` + +- [ ] **Step 4: Run tests** + +Run: `cd /opt/imagecreator/imagecreator/backend && pytest tests/test_security.py -v` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add -A && git commit -m "feat: add JWT and password security module" +``` + +--- + +### Task 4: Implement auth API & service + +**Files:** +- Modify: `/opt/imagecreator/imagecreator/backend/app/schemas/auth.py` +- Modify: `/opt/imagecreator/imagecreator/backend/app/services/auth.py` +- Modify: `/opt/imagecreator/imagecreator/backend/app/api/auth.py` +- Create: `/opt/imagecreator/imagecreator/backend/tests/test_auth_api.py` + +- [ ] **Step 1: Write failing test** + +```python +# tests/test_auth_api.py +import pytest +from fastapi.testclient import TestClient +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from app.database import Base, get_db +from app.main import app +from app.models.user import User +from app.core.security import hash_password + +engine = create_engine("sqlite:///:memory:") +TestSessionLocal = sessionmaker(bind=engine) + +def override_get_db(): + db = TestSessionLocal() + try: + yield db + finally: + db.close() + +app.dependency_overrides[get_db] = override_get_db +client = TestClient(app) + +def test_register_success(): + Base.metadata.create_all(bind=engine) + response = client.post("/api/auth/register", json={ + "username": "newuser", + "email": "new@example.com", + "password": "password123" + }) + assert response.status_code == 201 + data = response.json() + assert data["username"] == "newuser" + assert "id" in data + +def test_register_duplicate_username(): + Base.metadata.create_all(bind=engine) + db = TestSessionLocal() + user = User(username="existing", email="existing@example.com", hashed_password=hash_password("pass")) + db.add(user) + db.commit() + db.close() + + response = client.post("/api/auth/register", json={ + "username": "existing", + "email": "new@example.com", + "password": "password123" + }) + assert response.status_code == 400 + +def test_login_success(): + Base.metadata.create_all(bind=engine) + db = TestSessionLocal() + user = User(username="loginuser", email="login@example.com", hashed_password=hash_password("correctpassword")) + db.add(user) + db.commit() + db.close() + + response = client.post("/api/auth/login", json={ + "username": "loginuser", + "password": "correctpassword" + }) + assert response.status_code == 200 + data = response.json() + assert "access_token" in data + assert "token_type" in data + +def test_login_wrong_password(): + Base.metadata.create_all(bind=engine) + db = TestSessionLocal() + user = User(username="wrongpass", email="wrongpass@example.com", hashed_password=hash_password("correctpassword")) + db.add(user) + db.commit() + db.close() + + response = client.post("/api/auth/login", json={ + "username": "wrongpass", + "password": "wrongpassword" + }) + assert response.status_code == 401 +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd /opt/imagecreator/imagecreator/backend && pytest tests/test_auth_api.py -v` +Expected: FAIL - modules not found + +- [ ] **Step 3: Write schemas** + +```python +# app/schemas/auth.py +from pydantic import BaseModel, EmailStr + +class UserRegister(BaseModel): + username: str + email: EmailStr + password: str + +class UserLogin(BaseModel): + username: str + password: str + +class Token(BaseModel): + access_token: str + refresh_token: str + token_type: str = "bearer" + +class TokenRefresh(BaseModel): + refresh_token: str +``` + +```python +# app/schemas/user.py +from pydantic import BaseModel, EmailStr +from typing import Optional +from datetime import datetime +from uuid import UUID + +class UserBase(BaseModel): + username: str + email: EmailStr + +class UserCreate(UserBase): + password: str + +class UserResponse(UserBase): + id: UUID + created_at: datetime + + class Config: + from_attributes = True + +class UserUpdate(BaseModel): + email: Optional[EmailStr] = None +``` + +- [ ] **Step 4: Write auth service** + +```python +# app/services/auth.py +from typing import Optional +from sqlalchemy.orm import Session +from uuid import UUID + +from app.models.user import User +from app.core.security import hash_password, verify_password + +class AuthService: + def __init__(self, db: Session): + self.db = db + + def register(self, username: str, email: str, password: str) -> User: + # Check if user exists + existing = self.db.query(User).filter( + (User.username == username) | (User.email == email) + ).first() + if existing: + raise ValueError("Username or email already registered") + + hashed_password = hash_password(password) + user = User(username=username, email=email, hashed_password=hashed_password) + self.db.add(user) + self.db.commit() + self.db.refresh(user) + return user + + def authenticate(self, username: str, password: str) -> Optional[User]: + user = self.db.query(User).filter(User.username == username).first() + if not user: + return None + if not verify_password(password, user.hashed_password): + return None + return user + + def get_user_by_id(self, user_id: UUID) -> Optional[User]: + return self.db.query(User).filter(User.id == user_id).first() +``` + +- [ ] **Step 5: Write auth API routes** + +```python +# app/api/auth.py +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session + +from app.database import get_db +from app.schemas.auth import UserRegister, UserLogin, Token, TokenRefresh +from app.schemas.user import UserResponse +from app.services.auth import AuthService +from app.core.security import create_access_token + +router = APIRouter() + +@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED) +def register(data: UserRegister, db: Session = Depends(get_db)): + service = AuthService(db) + try: + user = service.register(data.username, data.email, data.password) + return user + except ValueError as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) + +@router.post("/login", response_model=Token) +def login(data: UserLogin, db: Session = Depends(get_db)): + service = AuthService(db) + user = service.authenticate(data.username, data.password) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password" + ) + + access_token = create_access_token(data={"sub": str(user.id)}) + # For simplicity, using same token as refresh - in production, use separate refresh token + return Token(access_token=access_token, refresh_token=access_token) + +@router.post("/refresh", response_model=Token) +def refresh(data: TokenRefresh, db: Session = Depends(get_db)): + # Simplified: in production, validate refresh token properly + from app.core.security import decode_access_token + payload = decode_access_token(data.refresh_token) + if not payload: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") + + access_token = create_access_token(data={"sub": payload.get("sub")}) + return Token(access_token=access_token, refresh_token=data.refresh_token) +``` + +- [ ] **Step 6: Write api/deps.py** + +```python +# app/api/deps.py +from typing import Generator +from fastapi import Depends, HTTPException, status +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from sqlalchemy.orm import Session + +from app.database import get_db +from app.core.security import decode_access_token +from app.services.auth import AuthService + +security = HTTPBearer() + +def get_current_user( + credentials: HTTPAuthorizationCredentials = Depends(security), + db: Session = Depends(get_db) +): + token = credentials.credentials + payload = decode_access_token(token) + if not payload: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid or expired token" + ) + + user_id_str = payload.get("sub") + if not user_id_str: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") + + from uuid import UUID + try: + user_id = UUID(user_id_str) + except ValueError: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid user ID") + + service = AuthService(db) + user = service.get_user_by_id(user_id) + if not user: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found") + + return user +``` + +- [ ] **Step 7: Run tests** + +Run: `cd /opt/imagecreator/imagecreator/backend && pytest tests/test_auth_api.py -v` +Expected: PASS + +- [ ] **Step 8: Commit** + +```bash +git add -A && git commit -m "feat: implement auth API (register, login, JWT)" +``` + +--- + +### Task 5: Implement users API + +**Files:** +- Modify: `/opt/imagecreator/imagecreator/backend/app/api/users.py` +- Create: `/opt/imagecreator/imagecreator/backend/tests/test_users_api.py` + +- [ ] **Step 1: Write failing test** + +```python +# tests/test_users_api.py +import pytest +from fastapi.testclient import TestClient +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from app.database import Base, get_db +from app.main import app +from app.models.user import User +from app.core.security import hash_password, create_access_token + +engine = create_engine("sqlite:///:memory:") +TestSessionLocal = sessionmaker(bind=engine) + +def override_get_db(): + db = TestSessionLocal() + try: + yield db + finally: + db.close() + +app.dependency_overrides[get_db] = override_get_db +client = TestClient(app) + +def get_auth_header(user_id): + token = create_access_token({"sub": str(user_id)}) + return {"Authorization": f"Bearer {token}"} + +def test_get_current_user(): + Base.metadata.create_all(bind=engine) + db = TestSessionLocal() + user = User(username="testuser", email="test@example.com", hashed_password=hash_password("pass")) + db.add(user) + db.commit() + db.refresh(user) + db.close() + + response = client.get("/api/users/me", headers=get_auth_header(user.id)) + assert response.status_code == 200 + data = response.json() + assert data["username"] == "testuser" + assert data["email"] == "test@example.com" + assert "password" not in data +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd /opt/imagecreator/imagecreator/backend && pytest tests/test_users_api.py -v` +Expected: FAIL + +- [ ] **Step 3: Write users API** + +```python +# app/api/users.py +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session + +from app.database import get_db +from app.api.deps import get_current_user +from app.models.user import User +from app.schemas.user import UserResponse, UserUpdate +from app.services.auth import AuthService + +router = APIRouter() + +@router.get("/me", response_model=UserResponse) +def get_current_user_info(current_user: User = Depends(get_current_user)): + return current_user + +@router.put("/me", response_model=UserResponse) +def update_current_user( + data: UserUpdate, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + service = AuthService(db) + if data.email and data.email != current_user.email: + # Check if email is taken + existing = db.query(User).filter(User.email == data.email, User.id != current_user.id).first() + if existing: + raise HTTPException(status_code=400, detail="Email already registered") + current_user.email = data.email + + db.commit() + db.refresh(current_user) + return current_user +``` + +- [ ] **Step 4: Run tests** + +Run: `cd /opt/imagecreator/imagecreator/backend && pytest tests/test_users_api.py -v` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add -A && git commit -m "feat: implement users API" +``` + +--- + +### Task 6: Implement LLM and prompt optimization service + +**Files:** +- Modify: `/opt/imagecreator/imagecreator/backend/app/services/llm.py` +- Create: `/opt/imagecreator/imagecreator/backend/app/services/prompt.py` +- Create: `/opt/imagecreator/imagecreator/backend/tests/test_prompt.py` + +- [ ] **Step 1: Write failing test** + +```python +# tests/test_prompt.py +import pytest +from app.services.prompt import PromptOptimizationService + +def test_optimize_text2image_prompt(): + service = PromptOptimizationService() + result = service.optimize_text2image("a cat") + + assert result is not None + assert len(result) > len("a cat") + assert "cat" in result.lower() + +def test_optimize_image2image_prompt(): + service = PromptOptimizationService() + result = service.optimize_image2image("make it colorful", "a cat image") + + assert result is not None + assert len(result) > 0 +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd /opt/imagecreator/imagecreator/backend && pytest tests/test_prompt.py -v` +Expected: FAIL - module not found + +- [ ] **Step 3: Write LLM service** + +```python +# app/services/llm.py +from typing import Optional +import google.genai as genai +from app.config import settings + +class LLMService: + def __init__(self): + genai.configure(api_key=settings.GEMINI_API_KEY) + + def generate_text(self, prompt: str, system_instruction: Optional[str] = None) -> str: + client = genai.GenerativeModel("gemini-2.5-flash") + response = client.generate_content(prompt) + return response.text +``` + +```python +# app/services/prompt.py +from app.services.llm import LLMService + +TEXT2IMAGE_SYSTEM_PROMPT = """You are an expert at optimizing text-to-image prompts. +Your task is to take user's simple or vague prompt and enhance it with detailed, vivid descriptions. +Include details about: subject, setting, lighting, mood, style, composition, and quality modifiers. +Output ONLY the optimized prompt in English, no explanations.""" + +IMAGE2IMAGE_SYSTEM_PROMPT = """You are an expert at optimizing image-to-image prompts. +The user wants to modify an existing image. Your task is to clearly describe what should be changed. + +Important rules: +1. Identify if user wants to ADD, REMOVE, REPLACE, or ENHANCE something +2. Be specific about what to keep vs what to change +3. Describe the desired style, mood, and quality +4. Output ONLY the optimized prompt in English, no explanations.""" + +class PromptOptimizationService: + def __init__(self): + self.llm = LLMService() + + def optimize_text2image(self, user_prompt: str) -> str: + full_prompt = f"{TEXT2IMAGE_SYSTEM_PROMPT}\n\nUser's original prompt: {user_prompt}" + return self.llm.generate_text(full_prompt) + + def optimize_image2image(self, user_prompt: str, original_description: str = "") -> str: + context = f"Original image description: {original_description}\n\n" if original_description else "" + full_prompt = f"{IMAGE2IMAGE_SYSTEM_PROMPT}\n\n{context}User's modification request: {user_prompt}" + return self.llm.generate_text(full_prompt) +``` + +- [ ] **Step 4: Run tests (skipped if no API key)** + +Run: `cd /opt/imagecreator/imagecreator/backend && pytest tests/test_prompt.py -v` +Expected: SKIP or FAIL (if no API key) + +- [ ] **Step 5: Commit** + +```bash +git add -A && git commit -m "feat: implement LLM and prompt optimization service" +``` + +--- + +### Task 7: Implement image generation service + +**Files:** +- Modify: `/opt/imagecreator/imagecreator/backend/app/services/image.py` +- Create: `/opt/imagecreator/imagecreator/backend/tests/test_image_service.py` + +- [ ] **Step 1: Write failing test** + +```python +# tests/test_image_service.py +import pytest +from app.services.image import ImageGenerationService + +def test_generate_text2image_request_structure(): + # Test that the service creates proper request structure + # This is a unit test - actual generation would require API key + service = ImageGenerationService() + # Just verify service initializes + assert service is not None +``` + +- [ ] **Step 2: Write image service** + +```python +# app/services/image.py +import base64 +import os +import uuid +from typing import Optional +import google.genai as genai +from app.config import settings + +class ImageGenerationService: + def __init__(self): + genai.configure(api_key=settings.GEMINI_API_KEY) + + async def generate_text2image( + self, + prompt: str, + model: str = "gemini-2.5-flash-image" + ) -> dict: + client = genai.GenerativeModel(model) + + response = client.generate_content( + prompt, + generation_config={"response_modalities": ["TEXT", "IMAGE"]} + ) + + image_data = None + for part in response.candidates[0].content.parts: + if part.inline_data: + image_data = base64.b64encode(part.inline_data.data).decode('utf-8') + break + + return { + "image_b64": image_data, + "model": model, + "finish_reason": response.candidates[0].finish_reason + } + + async def generate_image2image( + self, + prompt: str, + input_image_b64: str, + input_mime_type: str = "image/png", + model: str = "gemini-2.5-flash-image" + ) -> dict: + client = genai.GenerativeModel(model) + + response = client.generate_content( + [ + {"text": prompt}, + {"inline_data": {"mime_type": input_mime_type, "data": input_image_b64}} + ], + generation_config={"response_modalities": ["TEXT", "IMAGE"]} + ) + + image_data = None + for part in response.candidates[0].content.parts: + if part.inline_data: + image_data = base64.b64encode(part.inline_data.data).decode('utf-8') + break + + return { + "image_b64": image_data, + "model": model, + "finish_reason": response.candidates[0].finish_reason + } + + def save_image(self, image_b64: str, user_id: str) -> str: + os.makedirs(settings.IMAGE_STORAGE_PATH, exist_ok=True) + filename = f"{user_id}/{uuid.uuid4()}.png" + filepath = os.path.join(settings.IMAGE_STORAGE_PATH, filename) + os.makedirs(os.path.dirname(filepath), exist_ok=True) + + image_data = base64.b64decode(image_b64) + with open(filepath, "wb") as f: + f.write(image_data) + + return f"/uploads/{filename}" +``` + +- [ ] **Step 3: Commit** + +```bash +git add -A && git commit -m "feat: implement image generation service" +``` + +--- + +### Task 8: Implement images API + +**Files:** +- Modify: `/opt/imagecreator/imagecreator/backend/app/schemas/image.py` +- Modify: `/opt/imagecreator/imagecreator/backend/app/api/images.py` +- Create: `/opt/imagecreator/imagecreator/backend/tests/test_images_api.py` + +- [ ] **Step 1: Write failing test** + +```python +# tests/test_images_api.py +import pytest +from fastapi.testclient import TestClient +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from app.database import Base, get_db +from app.main import app +from app.models.user import User +from app.core.security import hash_password, create_access_token + +engine = create_engine("sqlite:///:memory:") +TestSessionLocal = sessionmaker(bind=engine) + +def override_get_db(): + db = TestSessionLocal() + try: + yield db + finally: + db.close() + +app.dependency_overrides[get_db] = override_get_db +client = TestClient(app) + +def get_auth_header(user_id): + token = create_access_token({"sub": str(user_id)}) + return {"Authorization": f"Bearer {token}"} + +def test_optimize_prompt_requires_auth(): + response = client.post("/api/images/optimize", json={"prompt": "a cat"}) + assert response.status_code == 403 # No auth header + +def test_generate_requires_auth(): + response = client.post("/api/images/generate", json={ + "prompt": "a cat", + "mode": "text2image", + "model": "gemini-2.5-flash-image" + }) + assert response.status_code == 403 +``` + +- [ ] **Step 2: Write schemas** + +```python +# app/schemas/image.py +from pydantic import BaseModel +from typing import Optional +from enum import Enum + +class GenerationMode(str, Enum): + TEXT2IMAGE = "text2image" + IMAGE2IMAGE = "image2image" + +class PromptOptimizeRequest(BaseModel): + prompt: str + mode: GenerationMode = GenerationMode.TEXT2IMAGE + original_description: Optional[str] = None + +class PromptOptimizeResponse(BaseModel): + optimized_prompt: str + +class ImageGenerateRequest(BaseModel): + prompt: str + optimized_prompt: str + mode: GenerationMode + model: str = "gemini-2.5-flash-image" + input_image_b64: Optional[str] = None # For I2I + +class ImageGenerateResponse(BaseModel): + image_url: str + model: str + optimized_prompt: str +``` + +- [ ] **Step 3: Write images API** + +```python +# app/api/images.py +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session + +from app.database import get_db +from app.api.deps import get_current_user +from app.models.user import User +from app.schemas.image import ( + PromptOptimizeRequest, + PromptOptimizeResponse, + ImageGenerateRequest, + ImageGenerateResponse, +) +from app.services.prompt import PromptOptimizationService +from app.services.image import ImageGenerationService +from app.services.auth import AuthService + +router = APIRouter() + +@router.post("/optimize", response_model=PromptOptimizeResponse) +async def optimize_prompt( + data: PromptOptimizeRequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + service = PromptOptimizationService() + try: + if data.mode == "text2image": + optimized = service.optimize_text2image(data.prompt) + else: + optimized = service.optimize_image2image(data.prompt, data.original_description or "") + + return PromptOptimizeResponse(optimized_prompt=optimized) + except Exception as e: + raise HTTPException(status_code=500, detail=f"Optimization failed: {str(e)}") + +@router.post("/generate", response_model=ImageGenerateResponse) +async def generate_image( + data: ImageGenerateRequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + service = ImageGenerationService() + try: + if data.mode == "text2image": + result = await service.generate_text2image(data.optimized_prompt, data.model) + else: + if not data.input_image_b64: + raise HTTPException(status_code=400, detail="input_image_b64 required for image2image") + result = await service.generate_image2image( + data.optimized_prompt, + data.input_image_b64, + "image/png", + data.model + ) + + # Save image + image_url = service.save_image(result["image_b64"], str(current_user.id)) + + return ImageGenerateResponse( + image_url=image_url, + model=result["model"], + optimized_prompt=data.optimized_prompt + ) + except Exception as e: + raise HTTPException(status_code=500, detail=f"Generation failed: {str(e)}") +``` + +- [ ] **Step 4: Run tests** + +Run: `cd /opt/imagecreator/imagecreator/backend && pytest tests/test_images_api.py -v` +Expected: PASS (auth tests) + +- [ ] **Step 5: Commit** + +```bash +git add -A && git commit -m "feat: implement images API (optimize, generate)" +``` + +--- + +### Task 9: Implement history API + +**Files:** +- Modify: `/opt/imagecreator/imagecreator/backend/app/api/history.py` +- Create: `/opt/imagecreator/imagecreator/backend/tests/test_history_api.py` + +- [ ] **Step 1: Write failing test** + +```python +# tests/test_history_api.py +import pytest +from fastapi.testclient import TestClient +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from app.database import Base, get_db +from app.main import app +from app.models.user import User +from app.models.history import GenerationHistory, GenerationMode +from app.core.security import hash_password, create_access_token + +engine = create_engine("sqlite:///:memory:") +TestSessionLocal = sessionmaker(bind=engine) + +def override_get_db(): + db = TestSessionLocal() + try: + yield db + finally: + db.close() + +app.dependency_overrides[get_db] = override_get_db +client = TestClient(app) + +def get_auth_header(user_id): + token = create_access_token({"sub": str(user_id)}) + return {"Authorization": f"Bearer {token}"} + +def test_get_history_empty(): + Base.metadata.create_all(bind=engine) + db = TestSessionLocal() + user = User(username="histtest", email="hist@example.com", hashed_password=hash_password("pass")) + db.add(user) + db.commit() + db.refresh(user) + db.close() + + response = client.get("/api/history", headers=get_auth_header(user.id)) + assert response.status_code == 200 + assert response.json() == [] + +def test_get_history_with_items(): + Base.metadata.create_all(bind=engine) + db = TestSessionLocal() + user = User(username="histtest2", email="hist2@example.com", hashed_password=hash_password("pass")) + db.add(user) + db.commit() + db.refresh(user) + + history = GenerationHistory( + user_id=user.id, + original_prompt="a cat", + optimized_prompt="a fluffy orange cat", + mode=GenerationMode.TEXT2IMAGE, + model="gemini", + image_url="/images/cat.png" + ) + db.add(history) + db.commit() + db.close() + + response = client.get("/api/history", headers=get_auth_header(user.id)) + assert response.status_code == 200 + data = response.json() + assert len(data) == 1 + assert data[0]["original_prompt"] == "a cat" +``` + +- [ ] **Step 2: Write history API** + +```python +# app/api/history.py +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from typing import List +from uuid import UUID + +from app.database import get_db +from app.api.deps import get_current_user +from app.models.user import User +from app.models.history import GenerationHistory +from app.schemas.history import HistoryResponse + +router = APIRouter() + +@router.get("", response_model=List[HistoryResponse]) +def get_history( + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + items = db.query(GenerationHistory).filter( + GenerationHistory.user_id == current_user.id + ).order_by(GenerationHistory.created_at.desc()).all() + return items + +@router.get("/{history_id}", response_model=HistoryResponse) +def get_history_item( + history_id: UUID, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + item = db.query(GenerationHistory).filter( + GenerationHistory.id == history_id, + GenerationHistory.user_id == current_user.id + ).first() + + if not item: + raise HTTPException(status_code=404, detail="History item not found") + return item + +@router.delete("/{history_id}", status_code=status.HTTP_204_NO_CONTENT) +def delete_history_item( + history_id: UUID, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + item = db.query(GenerationHistory).filter( + GenerationHistory.id == history_id, + GenerationHistory.user_id == current_user.id + ).first() + + if not item: + raise HTTPException(status_code=404, detail="History item not found") + + db.delete(item) + db.commit() +``` + +- [ ] **Step 3: Write history schema** + +```python +# app/schemas/history.py +from pydantic import BaseModel +from typing import Optional +from datetime import datetime +from uuid import UUID + +class HistoryResponse(BaseModel): + id: UUID + original_prompt: str + optimized_prompt: str + mode: str + model: str + image_url: str + input_image_url: Optional[str] = None + created_at: datetime + + class Config: + from_attributes = True +``` + +- [ ] **Step 4: Run tests** + +Run: `cd /opt/imagecreator/imagecreator/backend && pytest tests/test_history_api.py -v` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add -A && git commit -m "feat: implement history API" +``` + +--- + +### Task 10: Backend Docker setup + +**Files:** +- Create: `/opt/imagecreator/imagecreator/backend/Dockerfile` +- Create: `/opt/imagecreator/imagecreator/docker-compose.yml` + +- [ ] **Step 1: Create backend Dockerfile** + +```dockerfile +# backend/Dockerfile +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY app/ ./app/ + +RUN apt-get update && apt-get install -y --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* + +# Create uploads directory +RUN mkdir -p /app/uploads + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] +``` + +- [ ] **Step 2: Create docker-compose.yml** + +```yaml +# docker-compose.yml +version: '3.8' + +services: + backend: + build: ./backend + ports: + - "8000:8000" + environment: + - DATABASE_URL=postgresql://user:pass@db:5432/imagecreator + - SECRET_KEY=change-this-secret-key-in-production + - GEMINI_API_KEY=${GEMINI_API_KEY} + - OPENAI_API_KEY=${OPENAI_API_KEY} + volumes: + - uploads:/app/uploads + depends_on: + - db + restart: unless-stopped + + frontend: + build: ./frontend + ports: + - "3000:80" + depends_on: + - backend + + db: + image: postgres:15-alpine + environment: + - POSTGRES_USER=user + - POSTGRES_PASSWORD=pass + - POSTGRES_DB=imagecreator + volumes: + - postgres_data:/var/lib/postgresql/data + restart: unless-stopped + +volumes: + postgres_data: + uploads: +``` + +- [ ] **Step 3: Create .env.example** + +```env +# .env.example +SECRET_KEY=your-secret-key-here +GEMINI_API_KEY=your-gemini-api-key +OPENAI_API_KEY=your-openai-api-key +DATABASE_URL=postgresql://user:pass@localhost:5432/imagecreator +``` + +- [ ] **Step 4: Commit** + +```bash +git add -A && git commit -m "feat: add backend Dockerfile and docker-compose" +``` + +--- + +## Phase 2: Frontend Development + +### Task 11: Initialize Vue 3 frontend project + +**Files:** +- Create: `/opt/imagecreator/imagecreator/frontend/package.json` +- Create: `/opt/imagecreator/imagecreator/frontend/vite.config.ts` +- Create: `/opt/imagecreator/imagecreator/frontend/index.html` +- Create: `/opt/imagecreator/imagecreator/frontend/tailwind.config.js` +- Create: `/opt/imagecreator/imagecreator/frontend/src/main.ts` +- Create: `/opt/imagecreator/imagecreator/frontend/src/App.vue` +- Create: `/opt/imagecreator/imagecreator/frontend/src/router/index.ts` +- Create: `/opt/imagecreator/imagecreator/frontend/src/api/index.ts` + +- [ ] **Step 1: Create package.json** + +```json +{ + "name": "imagecreator-frontend", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.4.0", + "vue-router": "^4.2.0", + "pinia": "^2.1.0", + "axios": "^1.6.0", + "naive-ui": "^2.38.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.0", + "typescript": "^5.3.0", + "vite": "^5.0.0", + "vue-tsc": "^1.8.0", + "tailwindcss": "^3.4.0", + "autoprefixer": "^10.4.0", + "postcss": "^8.4.0" + } +} +``` + +- [ ] **Step 2: Create vite.config.ts** + +```typescript +// vite.config.ts +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true + }, + '/uploads': { + target: 'http://localhost:8000', + changeOrigin: true + } + } + } +}) +``` + +- [ ] **Step 3: Create tailwind.config.js** + +```javascript +// tailwind.config.js +export default { + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} +``` + +- [ ] **Step 4: Create main.ts** + +```typescript +// src/main.ts +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import naive from 'naive-ui' +import App from './App.vue' +import router from './router' +import './style.css' + +const app = createApp(App) +app.use(createPinia()) +app.use(router) +app.use(naive) +app.mount('#app') +``` + +- [ ] **Step 5: Create api/index.ts** + +```typescript +// src/api/index.ts +import axios from 'axios' + +const api = axios.create({ + baseURL: '/api', + timeout: 30000, +}) + +api.interceptors.request.use((config) => { + const token = localStorage.getItem('access_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config +}) + +api.interceptors.response.use( + (response) => response, + async (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('access_token') + window.location.href = '/login' + } + return Promise.reject(error) + } +) + +export default api +``` + +- [ ] **Step 6: Create App.vue** + +```vue + + + + + + + + + + +``` + +- [ ] **Step 7: Create router** + +```typescript +// src/router/index.ts +import { createRouter, createWebHistory } from 'vue-router' + +const routes = [ + { path: '/login', name: 'Login', component: () => import('../views/Login.vue') }, + { path: '/register', name: 'Register', component: () => import('../views/Register.vue') }, + { path: '/', name: 'Home', component: () => import('../views/Home.vue'), meta: { requiresAuth: true } }, + { path: '/history', name: 'History', component: () => import('../views/History.vue'), meta: { requiresAuth: true } }, +] + +const router = createRouter({ + history: createWebHistory(), + routes, +}) + +router.beforeEach((to, from, next) => { + const token = localStorage.getItem('access_token') + if (to.meta.requiresAuth && !token) { + next('/login') + } else if ((to.path === '/login' || to.path === '/register') && token) { + next('/') + } else { + next() + } +}) + +export default router +``` + +- [ ] **Step 8: Create style.css** + +```css +/* src/style.css */ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} +``` + +- [ ] **Step 9: Commit** + +```bash +git add -A && git commit -m "feat: initialize Vue 3 frontend project" +``` + +--- + +### Task 12: Implement auth stores and API + +**Files:** +- Create: `/opt/imagecreator/imagecreator/frontend/src/stores/auth.ts` +- Create: `/opt/imagecreator/imagecreator/frontend/src/api/auth.ts` + +- [ ] **Step 1: Write auth API** + +```typescript +// src/api/auth.ts +import api from './index' + +export interface LoginRequest { + username: string + password: string +} + +export interface RegisterRequest { + username: string + email: string + password: string +} + +export interface AuthResponse { + access_token: string + refresh_token: string + token_type: string +} + +export const authApi = { + login(data: LoginRequest) { + return api.post('/auth/login', data) + }, + register(data: RegisterRequest) { + return api.post('/auth/register', data) + }, +} +``` + +- [ ] **Step 2: Write auth store** + +```typescript +// src/stores/auth.ts +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { authApi } from '../api/auth' + +export const useAuthStore = defineStore('auth', () => { + const token = ref(localStorage.getItem('access_token')) + const user = ref<{ id: string; username: string; email: string } | null>(null) + + const login = async (username: string, password: string) => { + const response = await authApi.login({ username, password }) + token.value = response.data.access_token + localStorage.setItem('access_token', response.data.access_token) + } + + const register = async (username: string, email: string, password: string) => { + await authApi.register({ username, email, password }) + } + + const logout = () => { + token.value = null + user.value = null + localStorage.removeItem('access_token') + } + + return { token, user, login, register, logout } +}) +``` + +- [ ] **Step 3: Commit** + +```bash +git add -A && git commit -m "feat: implement frontend auth store and API" +``` + +--- + +### Task 13: Implement Login and Register views + +**Files:** +- Create: `/opt/imagecreator/imagecreator/frontend/src/views/Login.vue` +- Create: `/opt/imagecreator/imagecreator/frontend/src/views/Register.vue` + +- [ ] **Step 1: Write Login.vue** + +```vue + + + + + + + + + + + + + 登录 + + + + 还没有账号?注册 + + + + + + +``` + +- [ ] **Step 2: Write Register.vue** + +```vue + + + + + + + + + + + + + + + + 注册 + + + + 已有账号?登录 + + + + + + +``` + +- [ ] **Step 3: Commit** + +```bash +git add -A && git commit -m "feat: implement Login and Register views" +``` + +--- + +### Task 14: Implement Home view with image generation + +**Files:** +- Create: `/opt/imagecreator/imagecreator/frontend/src/views/Home.vue` +- Create: `/opt/imagecreator/imagecreator/frontend/src/api/images.ts` +- Create: `/opt/imagecreator/imagecreator/frontend/src/components/ImageGenerator.vue` +- Create: `/opt/imagecreator/imagecreator/frontend/src/components/PromptOptimizer.vue` + +- [ ] **Step 1: Write images API** + +```typescript +// src/api/images.ts +import api from './index' + +export interface PromptOptimizeRequest { + prompt: string + mode: 'text2image' | 'image2image' + original_description?: string +} + +export interface ImageGenerateRequest { + prompt: string + optimized_prompt: string + mode: 'text2image' | 'image2image' + model: string + input_image_b64?: string +} + +export const imagesApi = { + optimizePrompt(data: PromptOptimizeRequest) { + return api.post<{ optimized_prompt: string }>('/images/optimize', data) + }, + generateImage(data: ImageGenerateRequest) { + return api.post<{ image_url: string; model: string }>('/images/generate', data) + }, +} +``` + +- [ ] **Step 2: Write Home.vue** + +```vue + + + + + + ImageCreator + + 生成 + 历史 + 退出 + + + + + + + + + + + + + + + + + 优化提示词 + + + + 优化后 + {{ optimizedPrompt }} + + + + + + 选择图片 + + + + + + + + + + + + + 下载图片 + + + + 生成的图片将显示在这里 + + + + + + 生成图片 + + + + + + + + + +``` + +- [ ] **Step 3: Commit** + +```bash +git add -A && git commit -m "feat: implement Home view with image generation" +``` + +--- + +### Task 15: Implement History view + +**Files:** +- Create: `/opt/imagecreator/imagecreator/frontend/src/views/History.vue` +- Create: `/opt/imagecreator/imagecreator/frontend/src/api/history.ts` + +- [ ] **Step 1: Write history API** + +```typescript +// src/api/history.ts +import api from './index' + +export interface HistoryItem { + id: string + original_prompt: string + optimized_prompt: string + mode: string + model: string + image_url: string + input_image_url: string | null + created_at: string +} + +export const historyApi = { + getHistory() { + return api.get('/history') + }, + deleteHistory(id: string) { + return api.delete(`/history/${id}`) + }, +} +``` + +- [ ] **Step 2: Write History.vue** + +```vue + + + + + + ImageCreator + + 生成 + 历史 + 退出 + + + + + + 生成历史 + + + + + + + 暂无历史记录 + + + + + + + + {{ item.mode === 'text2image' ? '文生图' : '图生图' }} + + 删除 + + + + + + + + 原始提示词: + {{ item.original_prompt }} + + + + 优化后: + {{ item.optimized_prompt }} + + + + + {{ formatDate(item.created_at) }} | {{ item.model }} + + + + + + + + + + +``` + +- [ ] **Step 3: Commit** + +```bash +git add -A && git commit -m "feat: implement History view" +``` + +--- + +### Task 16: Frontend Dockerfile and nginx config + +**Files:** +- Create: `/opt/imagecreator/imagecreator/frontend/Dockerfile` +- Create: `/opt/imagecreator/imagecreator/frontend/nginx.conf` + +- [ ] **Step 1: Create Dockerfile** + +```dockerfile +# frontend/Dockerfile +FROM node:20-alpine as builder + +WORKDIR /app +COPY package.json ./ +RUN npm install +COPY . . +RUN npm run build + +FROM nginx:alpine +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +``` + +- [ ] **Step 2: Create nginx.conf** + +```nginx +# frontend/nginx.conf +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://backend:8000/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + location /uploads/ { + proxy_pass http://backend:8000/uploads/; + proxy_set_header Host $host; + } +} +``` + +- [ ] **Step 3: Commit** + +```bash +git add -A && git commit -m "feat: add frontend Dockerfile and nginx config" +``` + +--- + +## Phase 3: Final Integration + +### Task 17: Create README + +**Files:** +- Create: `/opt/imagecreator/imagecreator/README.md` + +- [ ] **Step 1: Write README** + +```markdown +# ImageCreator + +A full-stack image generation website with user authentication, prompt optimization, and T2I/I2I generation. + +## Features + +- User authentication (register/login) +- Prompt optimization using LLM +- Text-to-Image generation +- Image-to-Image generation +- Generation history + +## Tech Stack + +- **Backend**: FastAPI, SQLAlchemy, PostgreSQL +- **Frontend**: Vue 3, Naive UI, TailwindCSS +- **Deployment**: Docker Compose + +## Quick Start + +### Prerequisites + +- Docker & Docker Compose +- Gemini API key (for image generation) + +### Environment Setup + +1. Copy `.env.example` to `.env` and configure: + ```bash + cp .env.example .env + ``` + +2. Edit `.env` and add your API keys: + ``` + GEMINI_API_KEY=your_gemini_api_key + SECRET_KEY=your-secret-key + ``` + +### Running with Docker Compose + +```bash +docker-compose up -d +``` + +- Frontend: http://localhost:3000 +- Backend API: http://localhost:8000 +- API Docs: http://localhost:8000/docs + +## Development + +### Backend + +```bash +cd backend +pip install -r requirements.txt +uvicorn app.main:app --reload --port 8000 +``` + +### Frontend + +```bash +cd frontend +npm install +npm run dev +``` + +## Project Structure + +``` +imagecreator/ +├── backend/ # FastAPI backend +│ ├── app/ +│ │ ├── api/ # API routes +│ │ ├── core/ # Security, config +│ │ ├── models/ # Database models +│ │ ├── schemas/ # Pydantic schemas +│ │ └── services/ # Business logic +│ └── tests/ +├── frontend/ # Vue 3 frontend +│ └── src/ +│ ├── api/ # API clients +│ ├── views/ # Page components +│ ├── stores/ # Pinia stores +│ └── router/ # Vue Router +└── docker-compose.yml +``` + +## License + +AGPL-3.0 +``` + +- [ ] **Step 2: Commit** + +```bash +git add -A && git commit -m "docs: add README" +``` + +--- + +## Summary + +**Total Tasks: 17** + +### Phase 1: Backend (Tasks 1-10) +- Project structure, config, database +- User & History models +- Security (JWT, password) +- Auth API & service +- Users API +- LLM & Prompt optimization service +- Image generation service +- Images API +- History API +- Backend Docker + +### Phase 2: Frontend (Tasks 11-16) +- Vue 3 project init +- Auth stores and API +- Login/Register views +- Home view with generation +- History view +- Frontend Docker + +### Phase 3: Final (Task 17) +- README documentation
{{ optimizedPrompt }}
原始提示词:
{{ item.original_prompt }}
优化后:
{{ item.optimized_prompt }}