A React application that extracts EXIF metadata from uploaded images and uses Google's Gemini AI to generate creative narrative stories based on the hidden data within your photos.
- πΈ Image Upload: Drag-and-drop image upload powered by Uppy
- π Metadata Extraction: Server-side EXIF metadata extraction from JPG, PNG, TIFF, and WEBP images
- π¨ Perceptual Hashing: Client-side visual fingerprinting for future duplicate detection
- π€ AI Story Generation: Creative narratives generated by Google Gemini based on image metadata
- πΎ Supabase Integration: Edge Functions for serverless metadata processing
- Node.js (v18 or higher recommended)
- Docker (for local Supabase development)
- Supabase CLI (install via
npm install -g supabase) - Google Gemini API Key (get one at aistudio.google.com)
npm installCreate a .env.local file in the project root:
GEMINI_API_KEY=your_gemini_api_key_here
supabase startThis starts a local Supabase instance with PostgreSQL and Edge Functions on port 54321.
npm run devThe app will be available at http://localhost:3000
/
βββ App.tsx # Main application component
βββ components/
β βββ UppyUploader.tsx # File upload interface
β βββ MetadataDisplay.tsx # Metadata and story display
β βββ Icons.tsx # SVG icons
βββ services/
β βββ geminiService.ts # Google Gemini AI integration
β βββ mediaService.ts # Perceptual hash generation
β βββ configService.ts # Metadata configuration loader
β βββ supabaseService.ts # Supabase client (stub)
βββ supabase/
β βββ functions/
β β βββ process-upload/ # Main metadata extraction endpoint
β β βββ extract-metadata/ # Alternative extraction endpoint
β βββ migrations/ # Database schema migrations
βββ metadata.json # EXIF tag configuration
βββ vite.config.ts # Vite bundler configuration
- User uploads an image via the Uppy interface
- Image is posted to Supabase Edge Function (
process-upload) - Server extracts EXIF metadata using the
exifrlibrary - Client generates a perceptual hash of the image
- Metadata is sent to Google Gemini for creative story generation
- Results are displayed in an interactive interface
sequenceDiagram
participant User
participant UppyUploader
participant EdgeFunction as Process-Upload<br/>(Edge Function)
participant MediaService
participant GeminiService
participant Display as MetadataDisplay
User->>UppyUploader: Upload Image
UppyUploader->>EdgeFunction: POST multipart/form-data
EdgeFunction->>EdgeFunction: Extract EXIF with exifr
EdgeFunction-->>UppyUploader: Return metadata JSON
UppyUploader->>MediaService: generatePerceptualHash(imageUrl)
MediaService->>MediaService: Generate blockhash
MediaService-->>UppyUploader: Return pHash
UppyUploader->>GeminiService: analyzeMetadataWithGemini()
GeminiService->>GeminiService: Call Gemini API
GeminiService-->>UppyUploader: Return AI story
UppyUploader->>Display: Display results
Display->>User: Show metadata & story
The app includes migrations for a media_metadata table that can store:
- File information (name, type, size)
- EXIF metadata (JSON)
- Perceptual hashes for duplicate detection
- Hamming distance function for similarity comparison
npm run buildOutput will be in the dist/ directory.
This project features comprehensive service documentation with JSDoc comments for IntelliSense support. All exported functions are extensively documented with parameters, return types, examples, and usage patterns.
Integrates with Google's Gemini AI to generate creative narrative stories from image metadata.
Key Function:
analyzeMetadataWithGemini(
embeddedMetadata: Record<string, any>,
providerMetadata: Record<string, any> | undefined,
config: AppConfig | null,
imageFormat: string
): Promise<string>Client-side perceptual hash generation for image fingerprinting and duplicate detection.
Key Function:
generatePerceptualHash(imageUrl: string): Promise<string>Generates a 64-bit perceptual hash using the blockhash algorithm (16-bit precision).
Loads and caches EXIF tag configuration with singleton pattern.
Key Function:
getConfig(): Promise<AppConfig>Fetches configuration once and caches for subsequent calls.
- process-upload: Receives multipart uploads from Uppy, extracts EXIF metadata
- extract-metadata: Alternative endpoint for raw binary uploads
graph TB
subgraph Frontend["Frontend (React)"]
App[App.tsx]
Uppy[UppyUploader.tsx]
ImageUploader[ImageUploader.tsx<br/>ALTERNATIVE]
Display[MetadataDisplay.tsx]
end
subgraph Services["Services Layer"]
Config[configService.ts]
Media[mediaService.ts]
Gemini[geminiService.ts]
end
subgraph Backend["Edge Functions"]
ProcessUpload[process-upload]
end
subgraph External["External APIs"]
GeminiAPI[Google Gemini API]
end
App --> Uppy
App -.alternative.-> ImageUploader
Uppy --> ProcessUpload
Uppy --> Media
Uppy --> Gemini
Gemini --> GeminiAPI
ImageUploader -.-> ProcessUpload
For comprehensive flowcharts, detailed service documentation, and data flow diagrams, see ARCHITECTURE.md.
- Frontend: React 19, TypeScript, Vite
- File Upload: Uppy (with native HTML5 alternative in ImageUploader)
- AI: Google Gemini 2.5 Flash
- Backend: Supabase Edge Functions (Deno)
- Database: PostgreSQL (via Supabase)
- Image Processing: exifr, blockhash-js
MIT
This project was scaffolded from Google AI Studio. View the original app at: https://ai.studio/apps/drive/19DcgZ9yN-Afb8vXOCHdy_wcMhEHdapEy