first commit

This commit is contained in:
2026-07-18 01:34:52 +05:30
commit d1308bf149
113 changed files with 20593 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
import type { AIProvider } from '@photo-gallery/sdk';
import { createClipProvider } from './clipProvider';
import { createFaceProvider } from './faceProvider';
import { createOCRProvider } from './ocrProvider';
import { createTensorflowProvider } from './tensorflowProvider';
/**
* The demo's AI provider, combining free in-browser capabilities + Gemini:
* - object detection: TensorFlow.js COCO-SSD, fully in-browser (no key)
* - face detection + recognition: face-api.js, in-browser → clustered into People
* - OCR: tesseract.js, in-browser → searchable text + the Documents album (no key)
* - background removal: @imgly, in-browser WASM (no key) — see generativeEdit
* - other generative edits: Gemini, proxied through /api/ai/edit so the API key
* stays server-side and is never exposed to the browser.
*/
export function createDemoAIProvider(): AIProvider {
const tf = createTensorflowProvider();
const face = createFaceProvider();
const ocr = createOCRProvider();
const clip = createClipProvider();
return {
name: 'demo-ai (coco-ssd + face-api + tesseract + clip + gemini)',
detectObjects: tf.detectObjects,
detectFaces: face.detectFaces,
ocr: ocr.ocr,
embedImage: clip.embedImage,
embedText: clip.embedText,
async generativeEdit(item, image, op) {
// Remove Background runs fully in-browser (free, no key) via @imgly — works
// even when Gemini is unavailable. Other ops go through the Gemini route.
if (op.type === 'remove-background') {
const inputBlob = await canvasBlob(image, 1600);
const { removeBackground } = await import('@imgly/background-removal');
return removeBackground(inputBlob, { output: { format: 'image/png' } });
}
const { data, mimeType } = imageToBase64(image, 1280);
const res = await fetch('/api/ai/edit', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ imageBase64: data, mimeType, op }),
});
if (!res.ok) {
const err = (await res.json().catch(() => ({}))) as { error?: string };
throw new Error(err.error || `AI request failed (${res.status}).`);
}
const { imageBase64, mimeType: outMime } = (await res.json()) as {
imageBase64: string;
mimeType?: string;
};
return base64ToBlob(imageBase64, outMime || 'image/png');
},
};
}
/** Draw an image to a canvas (downscaled) and return base64 JPEG (no data: prefix). */
function imageToBase64(
image: ImageBitmap | HTMLImageElement,
maxDim: number,
): { data: string; mimeType: string } {
const w = (image as HTMLImageElement).naturalWidth || (image as ImageBitmap).width;
const h = (image as HTMLImageElement).naturalHeight || (image as ImageBitmap).height;
const scale = Math.min(1, maxDim / Math.max(w, h));
const cw = Math.max(1, Math.round(w * scale));
const ch = Math.max(1, Math.round(h * scale));
const canvas = document.createElement('canvas');
canvas.width = cw;
canvas.height = ch;
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Canvas not supported.');
ctx.drawImage(image as CanvasImageSource, 0, 0, cw, ch);
const dataUrl = canvas.toDataURL('image/jpeg', 0.9);
return { data: dataUrl.split(',')[1] ?? '', mimeType: 'image/jpeg' };
}
/** Draw an image to a canvas (downscaled) and return a JPEG Blob. */
function canvasBlob(image: ImageBitmap | HTMLImageElement, maxDim: number): Promise<Blob> {
const w = (image as HTMLImageElement).naturalWidth || (image as ImageBitmap).width;
const h = (image as HTMLImageElement).naturalHeight || (image as ImageBitmap).height;
const scale = Math.min(1, maxDim / Math.max(w, h));
const cw = Math.max(1, Math.round(w * scale));
const ch = Math.max(1, Math.round(h * scale));
const canvas = document.createElement('canvas');
canvas.width = cw;
canvas.height = ch;
const ctx = canvas.getContext('2d');
if (!ctx) return Promise.reject(new Error('Canvas not supported.'));
ctx.drawImage(image as CanvasImageSource, 0, 0, cw, ch);
return new Promise((resolve, reject) =>
canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob failed'))), 'image/jpeg', 0.92),
);
}
function base64ToBlob(base64: string, mime: string): Blob {
const bin = atob(base64);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
return new Blob([bytes], { type: mime });
}