first commit
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
# AI setup — free & local (no paid Gemini required)
|
||||
|
||||
The gallery's AI splits into two groups. **Group 1 already runs for free, on-device, with no API key.**
|
||||
Only **Group 2** (generative pixel editing) ever needed Gemini — and that backend is now swappable to
|
||||
a free or fully-local model.
|
||||
|
||||
---
|
||||
|
||||
## Group 1 — Analysis & background removal — FREE, LOCAL, no key ✅
|
||||
|
||||
These run entirely in the user's browser (models download once, then cached). Nothing is sent to any
|
||||
server; no key or config is required. This is the bulk of "AI" in the app:
|
||||
|
||||
| Capability | Runs on | Library |
|
||||
|---|---|---|
|
||||
| Object detection → tags, Objects browser, auto object albums | in-browser (WebGL/WASM) | TensorFlow.js COCO-SSD |
|
||||
| Face detection + recognition → People clustering | in-browser | face-api.js |
|
||||
| OCR (text in images) → search + Documents album | in-browser | tesseract.js |
|
||||
| Semantic search / captions embeddings | in-browser | transformers.js (CLIP) |
|
||||
| **Remove Background** (editor) | in-browser (WASM) | @imgly/background-removal |
|
||||
|
||||
So imported photos get the **same analysis as captured ones**, and Remove Background works, **without
|
||||
Gemini or any key**. If you deploy with no AI keys at all, everything here still works.
|
||||
|
||||
---
|
||||
|
||||
## Group 2 — Generative pixel editing — pick a free or local backend
|
||||
|
||||
These edits *generate new pixels* from a prompt: **Restore, Colorize, Replace Sky, and free-form
|
||||
Prompt**. Pick a backend with the `AI_EDIT_PROVIDER` env var (default `auto`). The server route
|
||||
(`/api/ai/edit`) proxies to it so no key ever reaches the browser.
|
||||
|
||||
`auto` chooses the first configured of: **local → huggingface → gemini**.
|
||||
|
||||
### Option 1 — Your own local Stable Diffusion (free + private, recommended) 🏆
|
||||
|
||||
Run Stable Diffusion locally with any of **AUTOMATIC1111 WebUI**, **Forge**, or **SD.Next** — they all
|
||||
expose the same `img2img` HTTP API. Start it with the API enabled:
|
||||
|
||||
```bash
|
||||
# AUTOMATIC1111 example
|
||||
./webui.sh --api --listen # serves http://127.0.0.1:7860
|
||||
```
|
||||
|
||||
Then set on the app server:
|
||||
|
||||
```env
|
||||
AI_EDIT_PROVIDER=local
|
||||
LOCAL_SD_URL=http://127.0.0.1:7860
|
||||
# optional tuning:
|
||||
LOCAL_SD_DENOISE=0.55 # 0=keep original … 1=fully reimagine
|
||||
LOCAL_SD_STEPS=25
|
||||
LOCAL_SD_SAMPLER=Euler a
|
||||
```
|
||||
|
||||
100% free, unlimited, private (nothing leaves your machine), and works for every generative op. Needs
|
||||
a machine with a GPU (or a slow CPU). This is the best "own model, everything free" path.
|
||||
|
||||
> Deploying to Vercel? A serverless function can't reach your `localhost`. Either run the whole app on
|
||||
> the same box as SD (Option B in [DEPLOY-MANUAL.md](./DEPLOY-MANUAL.md)), or expose your SD box over a
|
||||
> tunnel (e.g. `cloudflared`, `ngrok`) and point `LOCAL_SD_URL` at the public URL.
|
||||
|
||||
### Option 2 — Hugging Face Inference API (free hosted, zero GPU)
|
||||
|
||||
Free token, no GPU needed. Uses an instruction image-editing model.
|
||||
|
||||
1. Create a free token at <https://huggingface.co/settings/tokens> (read scope is fine).
|
||||
2. Set:
|
||||
```env
|
||||
AI_EDIT_PROVIDER=huggingface
|
||||
HF_API_TOKEN=hf_xxxxxxxx
|
||||
HF_IMAGE_MODEL=timbrooks/instruct-pix2pix # optional; instruction-based image edit
|
||||
```
|
||||
Caveats of the free tier: the first call may return **503 "model loading"** (retry in ~20s), and
|
||||
there are rate limits. Great for a demo; for heavy use prefer Option 1. If a model becomes gated, pick
|
||||
another image-to-image model with `HF_IMAGE_MODEL`.
|
||||
|
||||
### Option 3 — Google Gemini (needs a *billed* key for images)
|
||||
|
||||
The **free** Gemini tier only returns text — image output (`gemini-2.5-flash-image` / "Nano Banana")
|
||||
requires billing enabled, which is why it fails for you. If you enable billing:
|
||||
|
||||
```env
|
||||
AI_EDIT_PROVIDER=gemini
|
||||
GEMINI_API_KEY=...
|
||||
GEMINI_IMAGE_MODEL=gemini-2.5-flash-image # optional
|
||||
```
|
||||
|
||||
### Option 4 — none
|
||||
|
||||
Leave all three unset (or `AI_EDIT_PROVIDER=none`). Generative edits show a friendly "not configured"
|
||||
message; **all of Group 1 (analysis + Remove Background) still works**, plus every non-AI editor tool
|
||||
(crop, rotate, filters, adjust, annotations, and the whole video editor).
|
||||
|
||||
---
|
||||
|
||||
## Bring your own provider (SDK-level)
|
||||
|
||||
The SDK takes a pluggable `AIProvider` (`ai` prop on `<PhotoGallery>`). Every method is optional:
|
||||
|
||||
```ts
|
||||
interface AIProvider {
|
||||
detectObjects?(item, image): DetectedObject[] | Promise<…>;
|
||||
detectFaces?(item, image): DetectedFace[] | Promise<…>;
|
||||
ocr?(item, image): string | Promise<string>;
|
||||
embedImage?(item, image): number[] | Promise<number[]>;
|
||||
embedText?(text): number[] | Promise<number[]>;
|
||||
generativeEdit?(item, image, op): Blob | Promise<Blob>; // return the edited image
|
||||
}
|
||||
```
|
||||
|
||||
The demo's provider (`apps/web/src/lib/ai/createDemoAIProvider.ts`) wires the in-browser models for
|
||||
analysis and background-removal, and routes the other generative ops to `/api/ai/edit`. To use a
|
||||
different service (local Ollama+vision, ComfyUI, Replicate, your own model server, etc.), implement
|
||||
`generativeEdit` (and/or the analysis methods) and pass your provider — no changes elsewhere.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Task | Free? | How |
|
||||
|---|---|---|
|
||||
| Object/face/OCR/embedding analysis | ✅ always free | in-browser, no key |
|
||||
| Remove Background | ✅ always free | in-browser (@imgly) |
|
||||
| Filters / crop / rotate / adjust / annotate / **video editor** | ✅ always free | no model at all |
|
||||
| Restore / Colorize / Replace-Sky / Prompt edit | ✅ free via **local SD** or **HF free token** | `AI_EDIT_PROVIDER` |
|
||||
@@ -0,0 +1,49 @@
|
||||
# Architecture
|
||||
|
||||
## Monorepo
|
||||
```
|
||||
packages/photo-sdk/ # the product (React + TS, self-contained CSS, no ML deps)
|
||||
src/
|
||||
components/ # PhotoGallery, AppShell, Sidebar, TopToolbar, views/, editor/, Lightbox,
|
||||
# MediaGrid, PhotoTile, ContextMenu, Modal, SelectionBar, AIAnalyzer, AnnotationLayer
|
||||
store/ # Zustand store (createGalleryStore) + selectors + React context
|
||||
adapters/ # StorageAdapter interface + localStorage default (Supabase planned)
|
||||
ai/ # AIProvider interface (detect/faces/ocr/caption/embed/generativeEdit) + helpers
|
||||
lib/ # classify, smartAlbums, grouping, edits, media (import/EXIF), format, download
|
||||
icons/ # original SF-style SVG set
|
||||
styles/sdk.css # design tokens (light/dark/semi-dark) + components, prefixed .apg-
|
||||
apps/web/ # Next.js demo
|
||||
src/app/ # landing, /gallery, /api/ai/edit (Gemini proxy), layout, middleware (nonce CSP)
|
||||
src/lib/ai/ # tensorflowProvider (coco-ssd), createDemoAIProvider (+ Gemini generativeEdit)
|
||||
src/lib/seed.ts # demo dataset (Picsum images + local video + synthetic metadata)
|
||||
```
|
||||
|
||||
## Data flow
|
||||
`<PhotoGallery>` → creates a per-instance Zustand store → `GalleryProvider` (context) → `AppShell` →
|
||||
Sidebar + Toolbar + `ViewRouter`. The store holds media/albums/people/selection/view/theme/AI status.
|
||||
A pluggable `StorageAdapter` loads/saves state (debounced). Inputs are normalized + sanitized through
|
||||
`normalizeMediaItem` (URL-scheme allow-list, string coercion) on both the `photos` prop and adapter load.
|
||||
|
||||
## Adapters (storage) — all optional
|
||||
`StorageAdapter = { name, load(), save(state), putBlob?(id, blob), clear?() }`.
|
||||
- Default: `createLocalStorageAdapter()` (metadata in localStorage, blobs in IndexedDB).
|
||||
- Planned: Supabase (Postgres + Storage), S3/R2/GCS/Azure/MinIO, REST/GraphQL. Swap via the `adapter` prop.
|
||||
|
||||
## AI providers — all optional, free unless noted
|
||||
`AIProvider` methods (each optional): `detectObjects`, `detectFaces`, `caption`, `ocr`, `embedImage`,
|
||||
`embedText`, `generativeEdit`. The SDK ships **no ML deps**; providers are injected by the host:
|
||||
- `tensorflowProvider` (COCO-SSD) — free, in-browser object detection.
|
||||
- `createDemoAIProvider` — combines detection + Gemini `generativeEdit` (proxied through `/api/ai/edit`).
|
||||
- Planned: face-api.js (faces), tesseract.js (OCR), transformers.js / Hugging Face (semantic search, captions).
|
||||
`AIAnalyzer` (headless) runs detection across the library and writes `objects`/`objectLabels` back.
|
||||
|
||||
## Theming
|
||||
CSS variables on `.apg[data-theme]`. Themes: `light`, `dark`, `semi-dark` (glass sidebar + light content).
|
||||
Customizable via props → CSS vars: `--apg-accent`, `--apg-radius*`. All feature toggles via `features`.
|
||||
|
||||
## Security
|
||||
Per-request **nonce CSP** in `apps/web/src/middleware.ts` (`script-src 'self' 'nonce-…' 'strict-dynamic'`,
|
||||
no `unsafe-inline`), plus X-Frame-Options/nosniff/Referrer/Permissions-Policy. API keys are server-side
|
||||
only (read in route handlers; never `NEXT_PUBLIC_`). Imports are type/scheme validated.
|
||||
|
||||
See [`ROADMAP.md`](ROADMAP.md) for status of every capability.
|
||||
@@ -0,0 +1,139 @@
|
||||
# Manual deployment (no Git) — the whole project (SDK + demo)
|
||||
|
||||
Use this when you want to deploy **without pushing to GitHub** — uploading the project straight from
|
||||
your machine. The monorepo ships the SDK (`packages/photo-sdk`) as TypeScript **source** that the demo
|
||||
compiles at build time (via Next's `transpilePackages`), so "the whole project" is just: build the
|
||||
demo, which pulls in the SDK automatically. You do **not** publish the SDK to npm.
|
||||
|
||||
Three options, easiest first. **Option A (Vercel CLI) is recommended** — it's a genuine manual upload
|
||||
(no Git), and it correctly runs this app's server features (per-request nonce CSP, dynamic rendering,
|
||||
the `/api/ai/edit` route).
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites (all options)
|
||||
|
||||
```bash
|
||||
pnpm install # once, at the repo root — links the SDK workspace
|
||||
pnpm build # sanity check: SDK (tsup) + Next production build both succeed
|
||||
```
|
||||
|
||||
Set up Supabase first (tables + the public `media` bucket) — see [`docs/DEPLOY.md`](./DEPLOY.md) §1.
|
||||
Have your `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` ready.
|
||||
|
||||
---
|
||||
|
||||
## Option A — Vercel CLI (manual upload, no Git) ✅ recommended
|
||||
|
||||
Uploads your local files directly to Vercel. No repository required.
|
||||
|
||||
```bash
|
||||
# 1. Install the CLI and sign in
|
||||
npm i -g vercel
|
||||
vercel login
|
||||
|
||||
# 2. From the REPO ROOT (so the SDK workspace + lockfile are included):
|
||||
cd d:/advance-photo-gallery-web-sdk
|
||||
vercel link # create/link a project (first time only)
|
||||
```
|
||||
|
||||
When `vercel link` / the first `vercel` run prompts:
|
||||
- **Set up and deploy?** → Yes
|
||||
- **Which scope?** → your account
|
||||
- **Link to existing project?** → No → give it a name
|
||||
- **In which directory is your code located?** → `./`
|
||||
- It detects Next.js. If it asks for **Root Directory**, set **`apps/web`**.
|
||||
(Or set it later: Vercel dashboard → Project → Settings → **Root Directory = `apps/web`**.)
|
||||
|
||||
```bash
|
||||
# 3. Add environment variables (or paste them in the dashboard → Settings → Env Vars)
|
||||
vercel env add NEXT_PUBLIC_SUPABASE_URL # paste your Project URL, choose Production+Preview
|
||||
vercel env add NEXT_PUBLIC_SUPABASE_ANON_KEY # paste your anon key
|
||||
# optional: NEXT_PUBLIC_SUPABASE_BUCKET (default "media"), and any AI keys (see docs/AI-SETUP.md)
|
||||
|
||||
# 4. Deploy to production
|
||||
vercel --prod
|
||||
```
|
||||
|
||||
Vercel prints a live URL. Re-run `vercel --prod` anytime to redeploy the current local code — still no
|
||||
Git involved. Uploads honor `.gitignore`/`.vercelignore`, so `node_modules`, `.next`, `.env.local`
|
||||
are not uploaded (Vercel installs + builds on its side).
|
||||
|
||||
> Netlify equivalent: `npm i -g netlify-cli`, `netlify deploy --build --prod` with the
|
||||
> `@netlify/plugin-nextjs` runtime and Base directory `apps/web`.
|
||||
|
||||
---
|
||||
|
||||
## Option B — Self-host on a Node server (manual upload / any Node host)
|
||||
|
||||
For a VPS, Render, Railway, Fly.io, or your own box. This runs `next start` (a Node server), which
|
||||
fully supports the SSR + CSP middleware.
|
||||
|
||||
```bash
|
||||
# On your machine:
|
||||
pnpm install
|
||||
pnpm build # builds the SDK + apps/web/.next
|
||||
```
|
||||
|
||||
Then run the demo as a Node process on the host:
|
||||
|
||||
```bash
|
||||
# The app must run from apps/web with production env vars set:
|
||||
cd apps/web
|
||||
NEXT_PUBLIC_SUPABASE_URL=... NEXT_PUBLIC_SUPABASE_ANON_KEY=... pnpm start # = next start -p 3000
|
||||
```
|
||||
|
||||
**What to upload** to the host (if copying files manually rather than building on the host): the whole
|
||||
repo, then run `pnpm install && pnpm build && pnpm --filter web start` there. The simplest reliable
|
||||
recipe is "clone/copy the repo → `pnpm install` → `pnpm build` → `pnpm --filter web start`", because
|
||||
`.next` and `node_modules` are environment-specific and shouldn't be copied between machines.
|
||||
|
||||
- **Render / Railway:** create a **Web Service**, Root = repo, Build = `pnpm install && pnpm build`,
|
||||
Start = `pnpm --filter web start`, add the env vars. (Both have a free tier; some CLIs also allow
|
||||
direct upload without Git.)
|
||||
- **Port:** hosts set `$PORT`; use `next start -p $PORT` if required.
|
||||
|
||||
---
|
||||
|
||||
## Option C — Static export to ANY static host (drag-and-drop) ⚠️ trade-off
|
||||
|
||||
If you want to drop a plain folder onto cheap static hosting (Netlify Drop, GitHub Pages, cPanel,
|
||||
S3…), the app must be exported as static HTML. **This app can't be exported as-is** because it uses a
|
||||
**per-request nonce Content-Security-Policy** (middleware + dynamic rendering), which requires a
|
||||
server. To static-export you must **disable that nonce CSP** — a real security downgrade. Only do this
|
||||
for a throwaway/offline demo, and prefer Option A/B for anything real.
|
||||
|
||||
If you accept the trade-off:
|
||||
|
||||
1. `apps/web/next.config.mjs` → add `output: 'export'` and `images: { unoptimized: true }`.
|
||||
2. Remove the dynamic-nonce coupling: delete/neutralize `apps/web/src/middleware.ts` and remove the
|
||||
`headers()`/nonce usage in `apps/web/src/app/layout.tsx` (static export can't read per-request
|
||||
headers). Optionally add a **static** CSP via `<meta http-equiv="Content-Security-Policy">` instead
|
||||
— weaker than the nonce CSP.
|
||||
3. The Gemini/AI **API route won't exist** in a static export (`/api/ai/edit` is a server route). The
|
||||
free **in-browser** AI (object detection, faces, OCR, background-removal) still works; hosted
|
||||
generative edits do not. See [`docs/AI-SETUP.md`](./AI-SETUP.md) for a fully client-side/local AI
|
||||
setup that needs no server route.
|
||||
4. Build the static site:
|
||||
```bash
|
||||
pnpm build
|
||||
# output: apps/web/out ← upload THIS folder to any static host / drag-drop
|
||||
```
|
||||
5. Upload `apps/web/out/` to your host.
|
||||
|
||||
> Because Supabase is reached directly from the browser (anon key + RLS), persistence still works from
|
||||
> a static host — you only lose the server-side AI route and the strict nonce CSP.
|
||||
|
||||
---
|
||||
|
||||
## Which should I use?
|
||||
|
||||
| Need | Use |
|
||||
|---|---|
|
||||
| Fastest real deploy, no Git, keeps security + AI route | **A — Vercel CLI** |
|
||||
| Your own server / full control | **B — Self-host Node** |
|
||||
| Must drag a folder to cheap static hosting | **C — Static export** (weaker CSP, no server AI) |
|
||||
|
||||
For all options, everything the app stores (photos, videos, tags, versions, comments, albums, edited
|
||||
copies) persists to **your** Supabase project — see [`docs/DEPLOY.md`](./DEPLOY.md) for the data model
|
||||
and the required SQL.
|
||||
@@ -0,0 +1,136 @@
|
||||
# Deploy everything FREE — step by step (fresh DB + storage + Vercel free domain)
|
||||
|
||||
Follow top to bottom. ~15 minutes. All free. No Git required.
|
||||
|
||||
**Two things to know first:**
|
||||
- A **local Stable Diffusion model CANNOT run on Vercel** (no GPU on the free tier). On Vercel, your
|
||||
free AI is: object detection / faces / OCR / **Remove Background** (all run in the visitor's browser,
|
||||
no key) + optionally **Hugging Face** (free token) for generative edits. See Step 6.
|
||||
- Vercel has no "drag a folder from File Explorer" upload for this app (it's a server app). The
|
||||
no-Git way to upload your local files is the **Vercel CLI** (Step 4). It's 4 commands.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Create a FRESH Supabase project (database + storage)
|
||||
|
||||
1. Go to <https://supabase.com> → **Start your project** → sign in (GitHub or email).
|
||||
2. **New project**: pick a name (e.g. `photo-gallery`), a **region near you**, set a **database
|
||||
password** (save it somewhere), plan = **Free**. Click **Create**. Wait ~2 minutes.
|
||||
|
||||
## Step 2 — Create the tables + storage bucket (one SQL paste)
|
||||
|
||||
1. In the project, left sidebar → **SQL Editor** → **New query**.
|
||||
2. Open the file `docs/supabase-setup.sql` from this project, **copy ALL of it**, paste into the query
|
||||
box, and click **Run**. You should see "Success."
|
||||
- This creates the metadata tables, the demo access policies, **and the public `media` storage
|
||||
bucket** (the bucket is what makes uploaded/edited/captured files survive a reload).
|
||||
3. Verify: left sidebar → **Storage** → you should see a bucket named **`media`** marked **Public**.
|
||||
|
||||
## Step 3 — Copy your Supabase keys
|
||||
|
||||
Left sidebar → **Project Settings** (gear) → **API**. Copy these two (keep the tab open):
|
||||
|
||||
- **Project URL** → e.g. `https://abcd1234.supabase.co`
|
||||
- **Project API keys → `anon` `public`** → a long string
|
||||
|
||||
(Never use the `service_role` key in the app — only the `anon` key belongs in the browser.)
|
||||
|
||||
## Step 4 — Deploy to Vercel with the CLI (uploads your local files, no Git)
|
||||
|
||||
Open a terminal (PowerShell) **in the project folder** `d:\advance-photo-gallery-web-sdk`.
|
||||
|
||||
```powershell
|
||||
# 4a. Sanity build (optional but recommended)
|
||||
pnpm install
|
||||
pnpm build
|
||||
|
||||
# 4b. Install the Vercel CLI and log in (free account)
|
||||
npm i -g vercel # if this errors on permissions, use: npx vercel (in place of "vercel" below)
|
||||
vercel login # opens the browser to sign in
|
||||
|
||||
# 4c. Deploy — run this from the REPO ROOT (d:\advance-photo-gallery-web-sdk)
|
||||
vercel
|
||||
```
|
||||
|
||||
Answer the `vercel` prompts:
|
||||
| Prompt | Answer |
|
||||
|---|---|
|
||||
| Set up and deploy "…"? | **Y** |
|
||||
| Which scope? | your account |
|
||||
| Link to existing project? | **N** |
|
||||
| What's your project's name? | e.g. `photo-gallery` |
|
||||
| In which directory is your code located? | **`apps/web`** ← important |
|
||||
| Auto-detected settings (Next.js) — override? | **N** |
|
||||
|
||||
It builds and gives you a **Preview URL**. (Monorepo note: pointing the directory at `apps/web` makes
|
||||
Vercel auto-install the whole pnpm workspace, including the SDK — nothing extra to do.)
|
||||
|
||||
## Step 5 — Add your keys, then deploy to production
|
||||
|
||||
1. Go to <https://vercel.com> → your project → **Settings → Environment Variables**. Add (for
|
||||
**Production** and **Preview**):
|
||||
|
||||
| Name | Value |
|
||||
|---|---|
|
||||
| `NEXT_PUBLIC_SUPABASE_URL` | your Project URL from Step 3 |
|
||||
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | your anon public key from Step 3 |
|
||||
| `NEXT_PUBLIC_SUPABASE_BUCKET` | `media` |
|
||||
|
||||
2. Back in the terminal, deploy to production:
|
||||
```powershell
|
||||
vercel --prod
|
||||
```
|
||||
3. Vercel prints your **free domain**: `https://<your-project>.vercel.app`. That's your live site.
|
||||
(Rename it under **Settings → Domains** if you like — still free.)
|
||||
|
||||
> To redeploy later after changing code: just run `vercel --prod` again. No Git, ever.
|
||||
|
||||
## Step 6 — (Optional) Free generative-edit AI on Vercel
|
||||
|
||||
Analysis + Remove Background already work with **no key**. For the generative edits (Restore /
|
||||
Colorize / Replace-Sky / Prompt) on Vercel, add a **free Hugging Face** token:
|
||||
|
||||
1. <https://huggingface.co/settings/tokens> → **New token** (read scope) → copy it.
|
||||
2. Vercel → Settings → Environment Variables, add:
|
||||
| Name | Value |
|
||||
|---|---|
|
||||
| `AI_EDIT_PROVIDER` | `huggingface` |
|
||||
| `HF_API_TOKEN` | `hf_...` (your token) |
|
||||
3. `vercel --prod` again.
|
||||
|
||||
(First use may say "model loading — try again in ~20s"; that's the free tier warming up.)
|
||||
**Prefer your own local Stable Diffusion?** It can't live on Vercel — run it on your PC and expose it
|
||||
with a tunnel (`cloudflared`/`ngrok`), then set `LOCAL_SD_URL` to the tunnel URL. Full details:
|
||||
[`docs/AI-SETUP.md`](./AI-SETUP.md). If you add nothing, generative edits just show a "not configured"
|
||||
note and everything else works.
|
||||
|
||||
## Step 7 — Verify
|
||||
|
||||
Open your `*.vercel.app` URL and check:
|
||||
- The gallery loads (0 errors).
|
||||
- Import a photo (Add photos → Choose files) → **hard-reload** → it's still there (it saved to
|
||||
Supabase). Storage → the `media` bucket now has files; Table editor → `gallery_media` has rows.
|
||||
- Edit a photo twice → Info panel shows **Version 3**; comments + versions survive reload.
|
||||
|
||||
---
|
||||
|
||||
## If a deploy fails
|
||||
|
||||
- **"Cannot find module @photo-gallery/sdk"** → the Root Directory wasn't set to `apps/web`. Fix:
|
||||
Vercel → Settings → **Build & Deployment → Root Directory = `apps/web`** (and ensure "Include files
|
||||
outside the root directory" is on — Vercel usually auto-enables it for pnpm workspaces). Redeploy.
|
||||
- **Build can't find pnpm** → Vercel → Settings → General → set install to `pnpm install` (it detects
|
||||
`pnpm-lock.yaml` automatically if present at the repo root — make sure that file exists).
|
||||
- **Images vanish after reload** → the Supabase env vars aren't set (Step 5), or Step 2's SQL wasn't
|
||||
run so the `media` bucket is missing.
|
||||
- **Gemini image edit fails** → the free Gemini tier is text-only; switch to `AI_EDIT_PROVIDER=huggingface`
|
||||
(Step 6) or run local SD.
|
||||
|
||||
---
|
||||
|
||||
## Want a literal drag-and-drop (File Explorer) upload instead?
|
||||
|
||||
That only works for a **static** build, which needs code changes (disabling the per-request security
|
||||
CSP) and loses the server AI route — see **Option C** in [`docs/DEPLOY-MANUAL.md`](./DEPLOY-MANUAL.md).
|
||||
The Vercel CLI above is strongly recommended instead: it uploads your files without Git and keeps the
|
||||
app fully working (persistence, security, AI route).
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
# Deploying for free (Vercel + Supabase free tier)
|
||||
|
||||
This app runs 100% on free tiers: **Vercel Hobby** (Next.js hosting) + **Supabase Free**
|
||||
(Postgres for metadata + Storage for the media files). No paid services are required.
|
||||
The in-browser AI (object detection, faces, OCR) runs on the user's device for free; only the
|
||||
optional Gemini generative edit needs a key (and can be left off).
|
||||
|
||||
> TL;DR — run the SQL once, push to GitHub, import into Vercel with **Root Directory = `apps/web`**,
|
||||
> set the two `NEXT_PUBLIC_SUPABASE_*` env vars, deploy. Everything (photos, videos, tags, versions,
|
||||
> comments, albums, edited copies) then persists to your Supabase project.
|
||||
|
||||
---
|
||||
|
||||
## 1. Supabase (database + file storage)
|
||||
|
||||
1. Create a project at <https://supabase.com> (free tier).
|
||||
2. Open **SQL Editor → New query**, paste all of [`docs/supabase-setup.sql`](./supabase-setup.sql),
|
||||
and **Run**. This creates the metadata tables, the demo RLS policies, **and the public `media`
|
||||
storage bucket** (the bucket is what makes uploaded/edited/captured files survive a reload —
|
||||
without it the app silently falls back to non-durable URLs).
|
||||
3. Confirm **Storage** now lists a bucket named `media` marked **Public**.
|
||||
4. **Project Settings → API** → copy:
|
||||
- **Project URL** → `NEXT_PUBLIC_SUPABASE_URL`
|
||||
- **anon public** key → `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||
(Never use the `service_role` key in the client — it stays server-side only, if used at all.)
|
||||
|
||||
> Free-tier note: a Supabase project **pauses after ~7 days idle**. Open the dashboard to unpause
|
||||
> before a demo. Storage free tier is 1 GB, Postgres 500 MB — plenty for a demo library.
|
||||
|
||||
## 2. Push the repo to GitHub
|
||||
|
||||
Commit everything **including `pnpm-lock.yaml`** at the repo root (Vercel uses it to detect pnpm and
|
||||
install the whole workspace). Do not commit `.env.local` (it's gitignored).
|
||||
|
||||
## 3. Vercel
|
||||
|
||||
1. <https://vercel.com> → **Add New → Project** → import your GitHub repo.
|
||||
2. **Configure the project:**
|
||||
| Setting | Value |
|
||||
|---|---|
|
||||
| **Root Directory** | `apps/web` |
|
||||
| **Framework Preset** | Next.js (auto-detected) |
|
||||
| **Install Command** | `pnpm install` (default) |
|
||||
| **Build Command** | `next build` (default) |
|
||||
| **Output** | `.next` (default) |
|
||||
| **Node.js Version** | 20.x |
|
||||
|
||||
> No separate SDK build step is needed: `@photo-gallery/sdk` is consumed from TypeScript source
|
||||
> via Next's `transpilePackages`, so `pnpm install` (which links the workspace) is enough.
|
||||
|
||||
3. **Environment Variables** (Project → Settings → Environment Variables), for Production + Preview:
|
||||
|
||||
**Client (safe to expose — required for persistence):**
|
||||
| Name | Value |
|
||||
|---|---|
|
||||
| `NEXT_PUBLIC_SUPABASE_URL` | your Project URL |
|
||||
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | your anon public key |
|
||||
| `NEXT_PUBLIC_SUPABASE_BUCKET` | `media` *(optional; default is `media`)* |
|
||||
|
||||
**Server-only (optional — do NOT prefix with `NEXT_PUBLIC`):**
|
||||
| Name | Value |
|
||||
|---|---|
|
||||
| `GEMINI_API_KEY` | a Gemini key *(only for the AI generative-edit button)* |
|
||||
| `GEMINI_IMAGE_MODEL` | `gemini-2.5-flash-image` *(optional)* |
|
||||
|
||||
Without the Supabase vars the app still runs, but on browser-local storage only (data won't sync
|
||||
across devices). Without `GEMINI_API_KEY` the generative-edit route returns 503 while all the free
|
||||
in-browser AI keeps working.
|
||||
|
||||
Optional theming/feature flags (`NEXT_PUBLIC_APG_*`) are documented in [`docs/ENV.md`](./ENV.md).
|
||||
|
||||
4. **Deploy.**
|
||||
|
||||
## 4. Verify after the first deploy
|
||||
|
||||
- Response headers include a per-request `content-security-policy` with a `nonce-…` (confirms the
|
||||
nonce CSP middleware works on Vercel — expected; the app renders dynamically, not as a static export).
|
||||
- Import a photo → its `src` becomes a `https://<project>.supabase.co/...` URL → **hard-reload**: it
|
||||
still shows. Same for a camera capture and an edited copy.
|
||||
- Edit a photo twice → the Info panel shows **Version 3**; reload → history + comments persist.
|
||||
- The **Objects** sidebar section fills in as on-device detection tags photos.
|
||||
|
||||
## What persists to the database
|
||||
|
||||
Everything is stored as JSONB per item (`gallery_media.data`) + binaries in Storage, so a reload
|
||||
restores it all: **tags, detected objects/labels, faces, versions[] (full edit history + audit log),
|
||||
comments[], albums, favorites, edits, and Save-as-Copy items** (each copy is a new row with its own
|
||||
2-entry history). System/smart albums (incl. the auto **object albums**) are regenerated on load, so
|
||||
they're intentionally not stored.
|
||||
|
||||
## Gotchas / limits (free tier)
|
||||
|
||||
- **Gemini AI edit + Vercel body limit:** Vercel Hobby caps request bodies at ~4.5 MB. The AI-edit
|
||||
route is capped accordingly and large images are downscaled client-side first; very large images
|
||||
may still be rejected. The free in-browser AI is unaffected.
|
||||
- **Single-user demo:** the Supabase adapter does a full-state sync (no auth). For multi-user, add
|
||||
Supabase Auth and scope rows per user before going to production (the RLS policies are open for the
|
||||
demo — tighten them with `auth.uid()`).
|
||||
- **In-browser video export** (trim/overlays/watermark/etc.) requires the source video to be readable
|
||||
for canvas capture. Same-origin (Supabase Storage with CORS, which is on by default) works; a
|
||||
cross-origin host without CORS headers can't be exported in-browser (the editor now surfaces a clear
|
||||
error instead of failing silently).
|
||||
- **Heavy AI libs** (TensorFlow.js / face-api / tesseract / transformers / imgly) are dynamically
|
||||
imported on the client only — they don't bloat the server bundle, but first analysis downloads model
|
||||
weights (cached afterwards).
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
# Environment variables — customizing the gallery
|
||||
|
||||
The demo (`apps/web`) reads `NEXT_PUBLIC_APG_*` variables at build time and passes them
|
||||
to `<PhotoGallery>`. **Everything is optional** — anything you don't set falls back to the
|
||||
SDK's built-in defaults (the macOS‑style light / dark / semi‑dark themes).
|
||||
|
||||
Put these in `apps/web/.env.local` (gitignored) and restart `pnpm dev`.
|
||||
|
||||
> Because Next.js only inlines `NEXT_PUBLIC_*` variables that are referenced statically, the
|
||||
> demo reads each one explicitly in [`apps/web/src/lib/galleryConfig.ts`](../apps/web/src/lib/galleryConfig.ts).
|
||||
> If you add a new token, add it there too.
|
||||
|
||||
---
|
||||
|
||||
## Feature flags — show/hide any capability
|
||||
|
||||
Each accepts `true` / `false` (or `1` / `0`). Unset = enabled (SDK default).
|
||||
|
||||
| Variable | Controls |
|
||||
| --- | --- |
|
||||
| `NEXT_PUBLIC_APG_EDITOR` | Photo + video editor |
|
||||
| `NEXT_PUBLIC_APG_CAMERA` | Custom camera (capture / record) |
|
||||
| `NEXT_PUBLIC_APG_AI` | In-browser AI (objects / faces / OCR / semantic) |
|
||||
| `NEXT_PUBLIC_APG_MAP` | Map view |
|
||||
| `NEXT_PUBLIC_APG_IMPORT` | Upload / import |
|
||||
| `NEXT_PUBLIC_APG_EXPORT` | Download / export |
|
||||
| `NEXT_PUBLIC_APG_SHARING` | Share links + Shared Albums + Activity |
|
||||
|
||||
```bash
|
||||
# e.g. ship a viewer-only gallery (no editing, no camera, no uploads)
|
||||
NEXT_PUBLIC_APG_EDITOR=false
|
||||
NEXT_PUBLIC_APG_CAMERA=false
|
||||
NEXT_PUBLIC_APG_IMPORT=false
|
||||
```
|
||||
|
||||
## Theme & appearance
|
||||
|
||||
| Variable | Type | Maps to |
|
||||
| --- | --- | --- |
|
||||
| `NEXT_PUBLIC_APG_THEME` | `system` \| `light` \| `dark` \| `semi-dark` | Initial theme |
|
||||
| `NEXT_PUBLIC_APG_ACCENT` | color | Accent (buttons, highlights, focus) |
|
||||
| `NEXT_PUBLIC_APG_RADIUS` | px number | Base corner radius for all rounded UI |
|
||||
| `NEXT_PUBLIC_APG_SIDEBAR_RADIUS` | px number | Sidebar panel corner radius |
|
||||
| `NEXT_PUBLIC_APG_BG_LIGHT` / `_DARK` | color **or gradient** | App / content background |
|
||||
| `NEXT_PUBLIC_APG_ELEVATED_LIGHT` / `_DARK` | color | Cards / raised surfaces |
|
||||
| `NEXT_PUBLIC_APG_SIDEBAR_BG_LIGHT` / `_DARK` | color (rgba for opacity) | Sidebar glass backdrop |
|
||||
| `NEXT_PUBLIC_APG_TEXT_LIGHT` / `_DARK` | color | Primary text |
|
||||
|
||||
- **Gradients**: any `_BG_*` value may be a full CSS gradient, e.g.
|
||||
`linear-gradient(160deg,#1a1a2e,#16213e)`.
|
||||
- **Opacity**: use `rgba(...)` (or `#rrggbbaa`) for translucency, e.g. a more/less see-through
|
||||
sidebar: `NEXT_PUBLIC_APG_SIDEBAR_BG_DARK=rgba(20,20,22,0.55)`.
|
||||
- Light values apply in **light** + **semi‑dark** (content); dark values apply in **dark** mode
|
||||
and to the **semi‑dark sidebar**.
|
||||
|
||||
### Example — branded dark gallery with a gradient background
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_APG_THEME=dark
|
||||
NEXT_PUBLIC_APG_ACCENT=#ff375f
|
||||
NEXT_PUBLIC_APG_RADIUS=14
|
||||
NEXT_PUBLIC_APG_SIDEBAR_RADIUS=18
|
||||
NEXT_PUBLIC_APG_BG_DARK=linear-gradient(165deg,#141018,#241426)
|
||||
NEXT_PUBLIC_APG_SIDEBAR_BG_DARK=rgba(30,20,38,0.6)
|
||||
NEXT_PUBLIC_APG_ELEVATED_DARK=#2a2030
|
||||
```
|
||||
|
||||
## Using the tokens directly (without env)
|
||||
|
||||
If you embed the SDK yourself, pass the same values as props — no env needed:
|
||||
|
||||
```tsx
|
||||
<PhotoGallery
|
||||
theme="dark"
|
||||
accentColor="#ff375f"
|
||||
themeTokens={{
|
||||
bgDark: 'linear-gradient(165deg,#141018,#241426)',
|
||||
sidebarBgDark: 'rgba(30,20,38,0.6)',
|
||||
sidebarRadius: 18,
|
||||
}}
|
||||
features={{ camera: false }}
|
||||
/>
|
||||
```
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
# ROADMAP — full feature checklist
|
||||
|
||||
Living source of truth for what's **done ✅**, **in progress 🚧**, and **planned ⬜**. Every feature is
|
||||
designed to be **optional via flags** (`features={{ … }}`) — a consuming project enables only what it wants.
|
||||
|
||||
> Update this file as work lands. It is the authoritative cross-developer / cross-AI record.
|
||||
|
||||
## Legend
|
||||
✅ done & verified · 🚧 in progress · ⬜ planned · 🔑 needs an API key/paid plan
|
||||
|
||||
---
|
||||
|
||||
## 1. UI & theming
|
||||
- ✅ macOS Photos shell: sidebar, top toolbar, content area, window chrome (optional)
|
||||
- ✅ Sidebar: Library, Collections, Pinned (Favourites/Recently Saved/Map/Videos/Screenshots/People & Pets/
|
||||
Recently Deleted w/ lock icon), Albums, Sharing (Shared Albums/Activity), Utilities, Projects
|
||||
- ✅ Light mode · ✅ Dark mode · ✅ **Semi-dark** (glass dark sidebar + light content)
|
||||
- ✅ **macOS vibrancy/"glass"**: translucent backdrop-blur menus, sidebar, toolbar, Info panel & search dropdown
|
||||
(theme-aware fills + hairline borders + `--apg-radius-menu`); `@supports` fallback to a solid fill where
|
||||
`backdrop-filter` is unavailable
|
||||
- ✅ Responsive desktop/laptop/tablet/mobile + ultra-narrow (Galaxy Fold ~280px); mobile drawer sidebar
|
||||
- ✅ Theme switch control in UI (Light/Dark/Semi-Dark/System menu) · ⬜ optional **weather-aware** auto theme
|
||||
- ✅ Theming via props: **accentColor** + **borderRadius** (CSS vars); ⬜ density/font presets
|
||||
- ✅ Accessibility: focus traps in overlays, ARIA dialog roles, keyboard nav, reduced-motion, contrast (AA)
|
||||
|
||||
## 2. Library, viewing & navigation
|
||||
- ✅ Library views: Years / Months / All Photos (Days grouping supported) — **date/time grouping**
|
||||
- ✅ Collections (Memories, Pinned, Albums, People, Featured, Shared, Recent Days, Trips, Utilities, Objects)
|
||||
- ✅ Adaptive responsive grid + zoom in/out, lazy images
|
||||
- ✅ **Lightbox** photo viewer (prev/next, keyboard, actions) — 🚧 polish to 100% macOS parity
|
||||
- ✅ **Video player** (native controls in lightbox) — 🚧 macOS-style custom controls/scrubber
|
||||
- ✅ Multi-select (click **toggles** select/deselect; **Select All ↔ Unselect All**), **Copy to Album** +
|
||||
**Move to Album** (album views), keyboard shortcuts (⌘A, Delete, F, Esc, Space)
|
||||
|
||||
## 3. Albums
|
||||
- ✅ Create / rename / delete / duplicate; nested folders; covers
|
||||
- ✅ Add / remove / move items; "Remove from Album"
|
||||
- ✅ Smart albums (rule engine): Favourites, Videos, Screenshots, RAW, Live, Panoramas, Recently Saved
|
||||
- ✅ **Object albums** (auto, from detection) via the Objects browser → object focus
|
||||
- ✅ **People** (auto, from face clustering) — populates the People & Pets view; click → person-focused grid
|
||||
- ✅ **Documents** smart album (auto, from OCR `hasText` rule) — in sidebar + Collections
|
||||
- ⬜ "Copy into tag folder" materialized albums (e.g. a real "Table" album) — currently virtual via object focus
|
||||
|
||||
## 4. Import & capture
|
||||
- ✅ Import via **file browse** + **drag-and-drop** (images & videos); accepted-type allow-list
|
||||
- ✅ Reads dimensions / video duration; source auto-classification (camera/screenshot/download/social/scan/ai)
|
||||
- ✅ **Real EXIF extraction** (GPS→location, DateTimeOriginal→capture date, Make/Model, lens, ISO) via `exifr`
|
||||
- ✅ **Custom camera**: capture photo, record video (getUserMedia), front/back switch, rule-of-thirds grid
|
||||
- ✅ Live annotation while capturing (shapes + measurement arrows; saved with the capture)
|
||||
- ✅ Detailed metadata: format, **extension**, byte size, dimensions, lat/lon, date/time, source (Info panel)
|
||||
- ✅ Geolocation detect on capture (Geolocation API, permission-gated, in the custom camera)
|
||||
- ✅ Object detect on **import AND capture** → auto-tag + object albums (AIAnalyzer runs on new media)
|
||||
|
||||
## 5. Map & places
|
||||
- ✅ Leaflet map, free tiles (OSM + Esri satellite), no API key
|
||||
- ✅ Map / Satellite / **Grid** modes; markers for located media; markers re-sync on library change
|
||||
- ✅ **macOS thumbnail pins** (photo cover + count badge + tail), **zoom-aware clustering**; tap a pin → that
|
||||
location's photos in a **date-grouped sheet**; any GPS item (incl. screenshots) pins automatically
|
||||
- ✅ **Grid tab = Memories mosaic**: date sections, full-width **auto-sliding hero** + irregular varied tiles
|
||||
- ✅ **Mini-map** in the photo info panel
|
||||
- Note: demo seed intentionally locates ~half the photos; real photos populate via EXIF GPS (§4)
|
||||
|
||||
## 6. Editor (photo & video)
|
||||
- ✅ Photo: Adjust (13 sliders), Filters (10 presets), Crop/Rotate/Flip — non-destructive
|
||||
- ✅ **Annotation layer**: rectangle, ellipse/oval, line, **arrow**, **double-arrow with center text**
|
||||
(measurement, e.g. "12 cm"), text, freehand — with **color**; in gallery edit + read-only in lightbox
|
||||
- ✅ Annotation in **custom camera** (live) and on **video** (markup layer in the video editor)
|
||||
- ✅ **Full video editor** — Adjust, Filters, **Trim**, **Markup** (annotations), **Overlay** (composite an image),
|
||||
**Audio** (mute original + add a music track w/ volume); exports a new clip via canvas + MediaRecorder + WebAudio
|
||||
(free, no ffmpeg); **Save** (overwrite) or **Save as Copy** (pick album), Cancel-confirm — mirrors the photo editor
|
||||
- ✅ Edited result persists (CSS-filter model + Gemini/canvas bakes; video bakes to a new webm)
|
||||
- ✅ **Save vs Save as Copy** (overwrite the photo, or create a new copy and pick its album) + **Cancel
|
||||
confirmation** when there are unsaved edits (Escape too)
|
||||
|
||||
## 7. AI (all optional; free unless marked 🔑)
|
||||
- ✅ **Object detection** — TensorFlow.js COCO-SSD, in-browser, no key. Powers tags/search/find-similar/Objects
|
||||
- ✅ **Generative editor** — Gemini (Remove BG / Restore / Colorize / Replace Sky / prompt). 🔑 needs image-gen
|
||||
quota (billing); free-tier key returns 429. Key is server-side only via `/api/ai/edit`
|
||||
- ✅ **Face detection + clustering** → People — `@vladmandic/face-api` (TF.js), in-browser, **no key**.
|
||||
Detects faces + 128-D descriptors during analysis; the SDK clusters them (`lib/cluster.ts`) into People.
|
||||
Click a person → library filters to their photos. Models lazy-loaded from jsDelivr (CSP-allow-listed).
|
||||
- ✅ **OCR** (tesseract.js, in-browser, **no key**) → text extracted per image, **searchable** (search a word
|
||||
printed inside a photo and it's found), feeds the **Documents** smart album. A per-word confidence filter
|
||||
rejects the gibberish tesseract emits for photos without text, so only real documents qualify.
|
||||
- ✅ **Semantic search** "show me snowy mountains" — CLIP (transformers.js, `Xenova/clip-vit-base-patch16`,
|
||||
in-browser, **no key**); image embeddings computed during analysis, query embedded on search, ranked by cosine
|
||||
and blended after keyword hits. Models from the HF CDN (allow-listed); ~512-D vectors persist with each item
|
||||
- ⬜ **Captions** (transformers.js BLIP or HF, free)
|
||||
- ⬜ Pluggable **Hugging Face** provider option (Inference API / transformers.js)
|
||||
|
||||
## 7b. Advanced-features backlog (audited; prioritized — next candidates)
|
||||
> From a full feature audit vs. macOS Photos. HIGH = most-requested / highest value.
|
||||
- ✅ **Custom video player** (glass scrubber, buffered/played, volume, PiP, fullscreen, keyboard)
|
||||
- ⬜ **HIGH — Lightbox zoom/pan** (scroll / double-click / pinch) for detail viewing
|
||||
- ⬜ **HIGH — Editable metadata** in the Info panel (caption, keywords/tags, title; bulk edit)
|
||||
- ⬜ **HIGH — Slideshow / Memories playback** (auto-advance + Ken Burns transitions)
|
||||
- ⬜ MED — Lightbox **filmstrip** of adjacent media; **timeline scrubber** to jump by date
|
||||
- ⬜ MED — **Keyboard-shortcut help** modal (`?`); album **drag-to-reorder**
|
||||
- ⬜ MED — **Skeleton loaders** on library load; selection-count bar inside the lightbox
|
||||
- ⬜ LOW — Smart-album **rule builder** UI; materialized tag albums; **print** dialog; import-from-URL; undo/redo beyond the editor; true windowed virtualization
|
||||
|
||||
## 8. Backend & storage (all optional adapters)
|
||||
- ✅ Default zero-config localStorage + IndexedDB adapter (`StorageAdapter` interface)
|
||||
- ✅ **Supabase**: Postgres (data) + Supabase Storage (blobs) adapter
|
||||
(`apps/web/src/lib/adapters/supabaseAdapter.ts`); imports/captures upload blobs via `putBlob`; metadata in
|
||||
JSONB tables. Wired in the demo when env vars are set; falls back to localStorage otherwise. **Run
|
||||
`docs/supabase-setup.sql` once** (creates tables + RLS + storage policy), then reload. 🔑 creds in `.env.local`.
|
||||
- ⬜ Alternate adapters documented as options: S3 / R2 / GCS / Azure / MinIO; Postgres/MySQL/SQLite/Mongo;
|
||||
Prisma/Drizzle; REST/GraphQL/WebSocket; NestJS/Fastify service; BullMQ/Redis workers; vector DB
|
||||
(Qdrant/Pinecone/Weaviate/Chroma) for semantic search
|
||||
|
||||
## 9. Search
|
||||
- ✅ Text search (name, place, tag, detected object, source)
|
||||
- ✅ Click-object → find every photo with it (object focus)
|
||||
- ✅ **OCR text search** — words printed inside an image are matched by the normal search
|
||||
- ✅ **Natural-language semantic search** (CLIP) — "looks like" matches blended after keyword hits (§7)
|
||||
|
||||
## 10. Cross-cutting
|
||||
- ✅ Security: nonce CSP, headers, input validation, URL allow-list, server-only keys, sanitization
|
||||
- ✅ Recycle bin (30-day), duplicate detection + merge, export/download + metadata
|
||||
- ✅ **Sharing**: Share a photo / multiple / one-or-more albums → generates a copyable link (+ Download);
|
||||
**Shared Albums** (copy-link / revoke) + **Activity** feed of shares. Client-side share records (localStorage).
|
||||
- ✅ **Recently Deleted password lock**: set/change/remove a password (Web Crypto SHA-256, device-local), sidebar
|
||||
lock↔unlock icon, lock screen prompts for the password; settable from the ⋯ menu or the trash header
|
||||
- ✅ Lazy-load `@supabase/supabase-js` (dynamic import) — /gallery first load trimmed 234 KB → 176 KB
|
||||
- ⬜ Performance: true windowing/virtualization for very large libraries (currently lazy + auto-fill)
|
||||
- ✅ Docs: README + `docs/SETUP.md` + `docs/ARCHITECTURE.md` + this roadmap + project-local `CLAUDE.md`
|
||||
- ✅ Testing: Playwright e2e (manual runs); ⬜ automated CI suite
|
||||
|
||||
## Web-meaningful UI (done 2026-06-29)
|
||||
- ✅ "All Projects / App Store" (iOS-only) → **"All Albums"** overview (My Albums + Create + Recent Days).
|
||||
- ✅ Sidebar Albums section: All Albums + New Album + user albums (indented).
|
||||
|
||||
## New requests
|
||||
- ✅ **Advanced video editor** (2026-07-07): **multi-segment trim** (keep several ranges, cut middles,
|
||||
per-segment **speed**), **image/watermark overlays that actually composite** (bug fixed), **text
|
||||
overlays**, **keyframe animation** (move/fade/scale/rotate over time), **crop + 90° rotate + flips
|
||||
for video**, **audio** (mute/volume + music + master fade in/out), **poster/thumbnail picker**, and
|
||||
**export quality** (480p/720p/1080p, fps). Built on a rewritten canvas+MediaRecorder **timeline
|
||||
engine** (`lib/videoBake.ts` + shared `lib/videoTimeline.ts`); no ffmpeg. Bake failures now surface a
|
||||
clear error instead of silently dropping edits.
|
||||
- ✅ **Auto object smart albums** (2026-07-07): one live smart album per detected object (Person, Car,
|
||||
Chair, Table…) in a new sidebar **Objects** section; Collections "Objects" cards open the album.
|
||||
Membership is resolved live; the album set refreshes on analysis completion.
|
||||
- ✅ **Upload / analysis / persistence fixes** (2026-07-07): reliable file-picker ("Choose files"
|
||||
button); uploaded photos now get the **same AI analysis as captures** (newest-first + drain-loop, no
|
||||
starvation); versioning **increments correctly and survives reload** (load-path no longer strips
|
||||
`versions`/`comments`); **Save-as-Copy carries its own history**; imports/captures persist **durable**
|
||||
URLs (no dead `blob:` after reload); Supabase setup now **creates the public `media` bucket**.
|
||||
- ✅ **Free deployment guide** — [`docs/DEPLOY.md`](./DEPLOY.md) (Vercel + Supabase free tier) +
|
||||
[`docs/DEPLOY-MANUAL.md`](./DEPLOY-MANUAL.md) (no-Git manual upload: Vercel CLI / self-host Node / static export).
|
||||
- ✅ **Swappable AI backend (no paid Gemini)** — [`docs/AI-SETUP.md`](./AI-SETUP.md): analysis
|
||||
(objects/faces/OCR/embeddings) + Remove Background already run FREE in-browser (no key); generative
|
||||
edits (`/api/ai/edit`) now pick a backend via `AI_EDIT_PROVIDER` = **local** (own Stable Diffusion —
|
||||
free+private), **huggingface** (free token), or **gemini**, with an `auto` fallback.
|
||||
- ✅ **Versioning + Audit Log** (photos **and** videos, 2026-07-02): every save appends a new version (v1 =
|
||||
original, **never overwritten**); each version carries a human-readable audit log of what changed. Info panel →
|
||||
**Version history** (per-version thumbnail preview, timestamp, expandable "What changed", **Restore**); sidebar
|
||||
**Versions & Audit** view browses all edited/commented items. Persisted inline on `MediaItem` → Supabase JSONB.
|
||||
- ✅ **Comments** (photos **and** videos, 2026-07-02): threaded comments with author + timestamp, add/delete, shown
|
||||
in the Info panel; persisted with the item. Author remembered locally (`apg:comment-author`).
|
||||
- ✅ Editor **straighten/tilt** slider + ✅ **interactive crop** (free + fixed-ratio: Square/4:3/3:4/16:9/9:16/Original;
|
||||
square stays square while dragging) — **bakes on Done** (canvas) and uploads the result to Supabase Storage
|
||||
- ✅ Search dropdown defaults: **Recently Viewed / Recently Edited / Recently Added** (focus the search box)
|
||||
- ✅ Import existing **IPTC/XMP keywords** + location on import (exifr iptc+xmp)
|
||||
- ✅ **Local Remove Background** (`@imgly/background-removal`) — runs fully in-browser (WASM), **no key**; the
|
||||
editor's Remove BG uses it directly (Gemini not required). Other generative ops still use Gemini.
|
||||
- ✅ Info mini-map click → opens full **Map centered** on the photo; ✅ **tag click → date-grouped images**
|
||||
- ✅ Camera annotations now applied to the captured still (saved); ✅ Undo/Clear in camera + editor
|
||||
- ✅ Geolocation tagged on capture (prefetched, permission-gated); ✅ device info in capture EXIF
|
||||
- ✅ Auto **Camera** + **Screenshots** albums (created on first capture/import of that source)
|
||||
- ✅ Recycle bin: 30-day retention enforced (purge on load); restore/permanent-delete sync to Supabase
|
||||
|
||||
## Suggested build order (remaining)
|
||||
1. ✅ Map/Annotation/EXIF/Info/Semi-dark/theming + ✅ Custom camera + ✅ web-meaningful UI (done 2026-06-29)
|
||||
2. ⬜ **Supabase adapter (Postgres + Storage)** ← next (needs creds; no localStorage reliance after)
|
||||
3. ⬜ Editor straighten + ratio crop; search defaults; import tags; local remove-bg; map/tag nav
|
||||
4. ⬜ Face clustering → People; OCR → Documents; semantic search (free, HF/transformers.js)
|
||||
5. ⬜ Video editor/annotations; macOS-parity viewer/player polish; perf virtualization; CI
|
||||
@@ -0,0 +1,73 @@
|
||||
# Developer setup & run guide
|
||||
|
||||
## Prerequisites
|
||||
- **Node ≥ 20** and **pnpm** (`corepack enable` ships pnpm with Node).
|
||||
- Git Bash / PowerShell on Windows both work; macOS/Linux fine.
|
||||
|
||||
## Install & run
|
||||
```bash
|
||||
pnpm install # installs all workspaces
|
||||
pnpm dev # Next.js demo on http://localhost:3000
|
||||
# / → landing page (has the "Open Gallery" button)
|
||||
# /gallery → the full gallery (40+ seeded photos/videos)
|
||||
```
|
||||
|
||||
## Build & verify
|
||||
```bash
|
||||
pnpm build:sdk # build the publishable SDK → packages/photo-sdk/dist (ESM, CJS, .d.ts, styles.css)
|
||||
pnpm build # SDK + Next production build
|
||||
pnpm start # serve the production build
|
||||
pnpm typecheck # type-check every package
|
||||
pnpm format # prettier
|
||||
```
|
||||
|
||||
## Environment / secrets
|
||||
Copy `.env.example` → `apps/web/.env.local` (gitignored — never commit). Keys are **server-side only**.
|
||||
|
||||
```bash
|
||||
# Generative AI editor (optional). Requires a Gemini key with IMAGE-GENERATION quota (billing enabled);
|
||||
# a free-tier key authenticates but returns HTTP 429 for image output.
|
||||
GEMINI_API_KEY=your-key
|
||||
GEMINI_IMAGE_MODEL=gemini-2.5-flash-image
|
||||
|
||||
# Supabase backend (optional, planned adapter):
|
||||
# NEXT_PUBLIC_SUPABASE_URL=...
|
||||
# SUPABASE_SERVICE_ROLE_KEY=... # server-side only
|
||||
```
|
||||
|
||||
## Using the SDK in your app
|
||||
```tsx
|
||||
'use client';
|
||||
import { PhotoGallery } from '@photo-gallery/sdk';
|
||||
import '@photo-gallery/sdk/styles.css';
|
||||
|
||||
<PhotoGallery
|
||||
photos={myPhotos}
|
||||
theme="system" // 'light' | 'dark' | 'semi-dark' | 'system'
|
||||
accentColor="#0a84ff"
|
||||
borderRadius={10}
|
||||
features={{ editor: true, camera: true, ai: true, map: true, import: true, export: true }}
|
||||
adapter={myStorageAdapter} // optional; default = localStorage
|
||||
ai={myAIProvider} // optional; object detection / generative edit / faces / OCR / search
|
||||
/>
|
||||
```
|
||||
Mount in a **Client Component** (uses browser APIs). Import `leaflet/dist/leaflet.css` once if using the Map.
|
||||
|
||||
## Feature flags
|
||||
Everything is optional. Pass `features={{ … : false }}` to hide a capability. See
|
||||
[`ROADMAP.md`](ROADMAP.md) for the full list and [`ARCHITECTURE.md`](ARCHITECTURE.md) for adapters/providers.
|
||||
|
||||
## Project memory (important)
|
||||
This repo carries its own AI/developer memory in the root — **`CLAUDE.md`** + this `docs/` folder — *not*
|
||||
in any individual machine's `~/.claude`. So if another developer (or a different Claude/AI account) opens
|
||||
this package, the full context, decisions and roadmap are available. Keep `docs/ROADMAP.md` updated as you
|
||||
work; treat it as the authoritative status.
|
||||
|
||||
## Testing
|
||||
Playwright (via MCP) is used for e2e checks (navigate, screenshot, console). A scripted CI suite is planned.
|
||||
|
||||
## Troubleshooting
|
||||
- **pnpm build-scripts blocked** (esbuild/sharp/core-js): they're allow-listed in `pnpm-workspace.yaml`
|
||||
(`allowBuilds`); run `pnpm rebuild esbuild sharp` if needed.
|
||||
- **Map tiles/photos blank**: check the CSP `connect-src`/`img-src` in `apps/web/src/middleware.ts`.
|
||||
- **AI edit returns 429**: Gemini image generation needs billing — see Environment above.
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,57 @@
|
||||
-- Photo Gallery SDK — Supabase setup.
|
||||
-- Run ONCE in your Supabase project: Dashboard → SQL Editor → New query → paste → Run.
|
||||
-- Creates the metadata tables + permissive demo RLS policies + anon upload policy for the
|
||||
-- public `media` storage bucket. NOTE: these policies are open (no auth) for the demo —
|
||||
-- add Supabase Auth and tighten the policies (e.g. `auth.uid()`) before production.
|
||||
|
||||
create table if not exists public.gallery_media (
|
||||
id text primary key,
|
||||
data jsonb not null,
|
||||
taken_at bigint,
|
||||
deleted_at bigint,
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
create table if not exists public.gallery_albums (
|
||||
id text primary key,
|
||||
data jsonb not null,
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
create table if not exists public.gallery_people (
|
||||
id text primary key,
|
||||
data jsonb not null,
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
alter table public.gallery_media enable row level security;
|
||||
alter table public.gallery_albums enable row level security;
|
||||
alter table public.gallery_people enable row level security;
|
||||
|
||||
-- Demo policies: allow the anon role full access. Replace with auth-scoped policies for production.
|
||||
drop policy if exists "demo_all" on public.gallery_media;
|
||||
drop policy if exists "demo_all" on public.gallery_albums;
|
||||
drop policy if exists "demo_all" on public.gallery_people;
|
||||
create policy "demo_all" on public.gallery_media for all to anon using (true) with check (true);
|
||||
create policy "demo_all" on public.gallery_albums for all to anon using (true) with check (true);
|
||||
create policy "demo_all" on public.gallery_people for all to anon using (true) with check (true);
|
||||
|
||||
-- Storage: CREATE the public `media` bucket (this is the linchpin — without it every
|
||||
-- upload fails and images fall back to non-durable blob:/data: URLs). Public = reads
|
||||
-- work via getPublicUrl. Re-running is safe (idempotent) and re-asserts public + limits.
|
||||
insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
|
||||
values (
|
||||
'media', 'media', true, 209715200,
|
||||
array['image/jpeg','image/png','image/gif','image/webp','image/avif','image/bmp','image/tiff',
|
||||
'video/mp4','video/quicktime','video/webm','video/x-matroska','video/3gpp']
|
||||
)
|
||||
on conflict (id) do update
|
||||
set public = true,
|
||||
file_size_limit = excluded.file_size_limit,
|
||||
allowed_mime_types = excluded.allowed_mime_types;
|
||||
|
||||
-- Allow anon to read/upload/update objects in the `media` bucket.
|
||||
drop policy if exists "media_read" on storage.objects;
|
||||
drop policy if exists "media_insert" on storage.objects;
|
||||
drop policy if exists "media_update" on storage.objects;
|
||||
create policy "media_read" on storage.objects for select to anon using (bucket_id = 'media');
|
||||
create policy "media_insert" on storage.objects for insert to anon with check (bucket_id = 'media');
|
||||
create policy "media_update" on storage.objects for update to anon using (bucket_id = 'media') with check (bucket_id = 'media');
|
||||
Reference in New Issue
Block a user