Skip to content

Latest commit

 

History

History
425 lines (362 loc) · 10.7 KB

File metadata and controls

425 lines (362 loc) · 10.7 KB

Frontend Documentation

Overview

The frontend is a React 18 application built with TypeScript and Vite. It provides an interactive UI for managing photobooks, viewing images, exploring the Knowledge Graph, and managing person identities.

Directory Structure

frontend/
├── index.html              # HTML entry point
├── package.json            # Dependencies and scripts
├── vite.config.ts          # Vite configuration
├── tsconfig.json           # TypeScript configuration
├── tailwind.config.js      # Tailwind CSS configuration
├── postcss.config.js       # PostCSS configuration
│
└── src/
    ├── main.tsx            # React entry point
    ├── App.tsx             # Main app with routing
    ├── index.css           # Global styles and Tailwind imports
    │
    ├── pages/
    │   ├── LoginPage.tsx        # Authentication
    │   ├── RegisterPage.tsx     # User registration
    │   ├── DashboardPage.tsx    # Main dashboard (images/photobooks)
    │   ├── PersonsPage.tsx      # Person management
    │   ├── ObjectsPage.tsx      # Object browser
    │   ├── JobsDashboard.tsx    # Background job monitoring
    │   └── KGVisualizationPage.tsx  # Knowledge Graph explorer
    │
    ├── components/
    │   ├── features/           # Domain-specific components
    │   │   ├── ImageUploader.tsx      # Drag-drop file upload
    │   │   ├── ImageGallery.tsx       # Image grid with filters
    │   │   ├── ImageDetailModal.tsx   # Full-size image viewer
    │   │   ├── PhotobookCard.tsx      # Photobook preview card
    │   │   ├── CreatePhotobookModal.tsx # Photobook creation
    │   │   ├── FaceClustersPanel.tsx  # Person face clusters
    │   │   └── ...
    │   │
    │   ├── ui/                 # Reusable UI primitives
    │   │   ├── Button.tsx
    │   │   ├── Card.tsx
    │   │   ├── Spinner.tsx
    │   │   ├── Modal.tsx
    │   │   └── ...
    │   │
    │   ├── LoadingScreen.tsx   # Full-page loading state
    │   └── AuthLayout.tsx      # Auth page layout
    │
    ├── context/
    │   ├── AuthContext.tsx     # Auth state provider
    │   └── useAuth.ts          # Auth hook
    │
    ├── hooks/
    │   ├── usePhotobooks.ts    # Photobook CRUD hooks
    │   ├── useImages.ts        # Image query hooks
    │   ├── usePersons.ts       # Person/face hooks
    │   └── ...
    │
    ├── lib/
    │   └── api/
    │       ├── index.ts        # API exports
    │       ├── client.ts       # Fetch wrapper with auth
    │       ├── types.ts        # TypeScript interfaces
    │       ├── auth.ts         # Auth API calls
    │       ├── images.ts       # Image API calls
    │       ├── photobooks.ts   # Photobook API calls
    │       ├── persons.ts      # Person API calls
    │       └── kg.ts           # Knowledge Graph API
    │
    └── test/                   # Test utilities

Key Pages

DashboardPage

Main landing page with two tabs:

  • Photobooks: Grid of photobook cards with creation modal
  • Images: Image gallery with upload, face filtering, and detail views

Features:

  • Face cluster panel for filtering by person
  • "Show All Faces" toggle to overlay face bounding boxes
  • Image detail modal with full metadata and claims

PersonsPage

Person identity management:

  • List of all identified persons with face thumbnails
  • Person details with all associated images
  • Merge persons capability
  • Rename person
  • Unidentified faces queue for assignment

ObjectsPage

Browse detected objects across all images:

  • Filter by object type
  • View object instances with source images
  • Click to see object in context

KGVisualizationPage

Interactive Knowledge Graph explorer using Sigma.js:

  • Node filtering by type (IMAGE, PERSON, DETECTION, etc.)
  • Edge filtering by claim status
  • Confidence threshold slider
  • Search nodes by label
  • Click nodes/edges to view details
  • ForceAtlas2 layout algorithm

JobsDashboard

Background job monitoring:

  • Pending jobs queue
  • Recent completed jobs
  • Job status and results
  • Cancel pending jobs

State Management

React Query (TanStack Query)

Server state is managed with React Query for caching, refetching, and optimistic updates:

// hooks/useImages.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { getImages, uploadImage, deleteImage } from '../lib/api/images'

export function useImages() {
  return useQuery({
    queryKey: ['images'],
    queryFn: getImages,
  })
}

export function useUploadImage() {
  const queryClient = useQueryClient()
  
  return useMutation({
    mutationFn: uploadImage,
    onSuccess: () => {
      queryClient.invalidateQueries(['images'])
    },
  })
}

Auth Context

Authentication state via React Context:

// context/AuthContext.tsx
const AuthContext = createContext<AuthContextValue>(...)

export function AuthProvider({ children }) {
  const [user, setUser] = useState<User | null>(null)
  const [isLoading, setIsLoading] = useState(true)
  
  // Check stored token on mount
  useEffect(() => {
    const token = localStorage.getItem('access_token')
    if (token) {
      fetchCurrentUser().then(setUser).finally(() => setIsLoading(false))
    }
  }, [])
  
  return (
    <AuthContext.Provider value={{ user, isAuthenticated: !!user, login, logout, isLoading }}>
      {children}
    </AuthContext.Provider>
  )
}

API Client

Base Client (lib/api/client.ts)

Fetch wrapper with auth headers and error handling:

const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000/api/v1'

export async function apiClient<T>(
  path: string,
  options: RequestInit = {}
): Promise<T> {
  const token = localStorage.getItem('access_token')
  
  const response = await fetch(`${API_BASE}${path}`, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      ...(token && { Authorization: `Bearer ${token}` }),
      ...options.headers,
    },
  })
  
  if (!response.ok) {
    throw new ApiError(response.status, await response.text())
  }
  
  return response.json()
}

Type Definitions (lib/api/types.ts)

export interface Image {
  id: string
  filename: string
  original_name: string
  file_path: string
  thumbnail_path: string | null
  width: number
  height: number
  created_at: string
  properties: ImageProperties
}

export interface Person {
  id: string
  name: string
  face_count: number
  image_count: number
  representative_face: FaceData | null
}

export interface GraphNode {
  id: string
  type: KGNodeType
  label: string
  properties: Record<string, unknown>
  created_at: string
}

export interface GraphEdge {
  id: string
  source: string
  target: string
  predicate: string
  confidence: number
  status: KGClaimStatus
  source_type: string
  literal_value: string | null
}

UI Components

Component Patterns

Button with variants:

<Button variant="primary" leftIcon={<Plus />}>Create</Button>
<Button variant="secondary" onClick={handleCancel}>Cancel</Button>
<Button variant="danger" isLoading={isDeleting}>Delete</Button>

Card with interactive state:

<Card variant="interactive" onClick={handleClick}>
  <CardHeader>Title</CardHeader>
  <CardBody>Content</CardBody>
</Card>

Modal with animated backdrop:

<Modal isOpen={isOpen} onClose={handleClose} title="Edit Person">
  <form onSubmit={handleSubmit}>
    {/* form fields */}
  </form>
</Modal>

Animations (Framer Motion)

Page transitions and micro-interactions:

<AnimatePresence mode="wait">
  <motion.div
    key={activeTab}
    initial={{ opacity: 0, x: 20 }}
    animate={{ opacity: 1, x: 0 }}
    exit={{ opacity: 0, x: -20 }}
    transition={{ duration: 0.2 }}
  >
    {/* tab content */}
  </motion.div>
</AnimatePresence>

Knowledge Graph Visualization

Built with Sigma.js and graphology:

import { SigmaContainer, useLoadGraph } from '@react-sigma/core'
import Graph from 'graphology'
import forceAtlas2 from 'graphology-layout-forceatlas2'

function GraphLoader({ nodes, edges }) {
  const loadGraph = useLoadGraph()
  
  useEffect(() => {
    const graph = new Graph()
    
    // Add nodes with styling
    nodes.forEach(node => {
      graph.addNode(node.id, {
        label: node.label,
        size: 8,
        color: getNodeColor(node.type),
        x: Math.random() * 100,
        y: Math.random() * 100,
      })
    })
    
    // Add edges
    edges.forEach(edge => {
      graph.addEdge(edge.source, edge.target, {
        label: edge.predicate,
        size: edge.confidence * 2,
      })
    })
    
    // Apply force-directed layout
    forceAtlas2.assign(graph, { iterations: 100 })
    
    loadGraph(graph)
  }, [nodes, edges])
  
  return null
}

Styling

Tailwind CSS

Utility-first styling with custom theme:

<div className="min-h-screen bg-surface-50">
  <header className="bg-white border-b border-stone-100">
    <div className="max-w-7xl mx-auto px-6 py-4">
      <h1 className="text-2xl font-bold text-stone-900">Title</h1>
    </div>
  </header>
</div>

Custom Theme (tailwind.config.js)

module.exports = {
  theme: {
    extend: {
      colors: {
        accent: {
          50: '#fef2f2',
          // ... color scale
          600: '#dc2626',
        },
        surface: {
          50: '#fafafa',
          // ... gray scale
        },
      },
    },
  },
}

Development

Running

cd frontend
npm install
npm run dev  # Start dev server on port 5173

Building

npm run build  # Output to dist/
npm run preview  # Preview production build

Environment Variables

Create .env.local:

VITE_API_URL=http://localhost:8000/api/v1

Adding a New Page

  1. Create page component in src/pages/MyPage.tsx
  2. Add route in src/App.tsx:
    <Route
      path="/mypage"
      element={
        <ProtectedRoute>
          <MyPage />
        </ProtectedRoute>
      }
    />
  3. Add navigation link in header/sidebar

Adding a New API Endpoint

  1. Add types in src/lib/api/types.ts
  2. Create API function in src/lib/api/myfeature.ts
  3. Export from src/lib/api/index.ts
  4. Create React Query hook in src/hooks/useMyFeature.ts

Testing

npm test          # Run tests
npm run test:ui   # Run with UI
npm run test:cov  # With coverage

Test utilities in src/test/:

  • renderWithProviders() - Wrap with QueryClient and Router
  • createMockImage() - Generate test image data
  • mockApiResponse() - Mock fetch responses