aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
358 lines (355 loc) • 11.6 kB
JavaScript
import vision from '@google-cloud/vision';
import { CacheService } from './cache-service.js';
import { ErrorHandler } from './error-handler.js';
// @ts-nocheck - Optional Google Cloud Vision dependency
class VisionService {
constructor(config) {
this.config = config;
this.client = null;
this.cache = new CacheService(config.redis);
this.errorHandler = new ErrorHandler();
this.removeBgApiKey = config.removeBg.apiKey;
this.initializeClient();
}
initializeClient() {
try {
if (this.config.googleCloud.keyFilename) {
this.client = new vision.ImageAnnotatorClient({
keyFilename: this.config.googleCloud.keyFilename
});
} else if (this.config.googleCloud.apiKey) {
this.client = new vision.ImageAnnotatorClient({
apiEndpoint: 'vision.googleapis.com',
credentials: {
client_email: 'vision-api@project.iam.gserviceaccount.com',
private_key: this.config.googleCloud.apiKey
}
});
}
} catch (error) {
console.warn('Google Vision client initialization failed, using fallback:', error);
}
}
async detectFaces(imageBuffer) {
const cacheKey = `vision:faces:${this.hashBuffer(imageBuffer)}`;
if (this.config.costOptimization.enableCaching) {
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
}
try {
if (!this.client) {
return this.fallbackFaceDetection();
}
const [result] = await this.client.faceDetection(imageBuffer);
const faces = result.faceAnnotations || [];
const detectionResults = faces.map(face => ({
boundingBox: this.convertBoundingPoly(face.boundingPoly),
confidence: face.detectionConfidence || 0,
emotions: {
joy: this.likelihoodToScore(face.joyLikelihood),
sorrow: this.likelihoodToScore(face.sorrowLikelihood),
anger: this.likelihoodToScore(face.angerLikelihood),
surprise: this.likelihoodToScore(face.surpriseLikelihood)
},
landmarks: face.landmarks?.map(landmark => ({
type: landmark.type || '',
position: landmark.position || {
x: 0,
y: 0,
z: 0
}
}))
}));
if (this.config.costOptimization.enableCaching) {
await this.cache.set(cacheKey, detectionResults, 3600);
}
return detectionResults;
} catch (error) {
return this.errorHandler.handleWithFallback(error, () => this.fallbackFaceDetection(), {
service: 'Vision',
operation: 'detectFaces'
});
}
}
async detectObjects(imageBuffer) {
const cacheKey = `vision:objects:${this.hashBuffer(imageBuffer)}`;
if (this.config.costOptimization.enableCaching) {
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
}
try {
if (!this.client) {
return this.fallbackObjectDetection();
}
const [result] = await this.client.objectLocalization(imageBuffer);
const objects = result.localizedObjectAnnotations || [];
const detectionResults = objects.map(obj => ({
name: obj.name || 'unknown',
confidence: obj.score || 0,
boundingBox: this.convertBoundingPoly(obj.boundingPoly)
}));
if (this.config.costOptimization.enableCaching) {
await this.cache.set(cacheKey, detectionResults, 3600);
}
return detectionResults;
} catch (error) {
return this.errorHandler.handleWithFallback(error, () => this.fallbackObjectDetection(), {
service: 'Vision',
operation: 'detectObjects'
});
}
}
async extractText(imageBuffer) {
const cacheKey = `vision:text:${this.hashBuffer(imageBuffer)}`;
if (this.config.costOptimization.enableCaching) {
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
}
try {
if (!this.client) {
return this.fallbackTextExtraction();
}
const [result] = await this.client.documentTextDetection(imageBuffer);
const fullTextAnnotation = result.fullTextAnnotation;
if (!fullTextAnnotation) {
return {
text: '',
confidence: 0,
blocks: []
};
}
const extractionResult = {
text: fullTextAnnotation.text || '',
confidence: this.calculateAverageConfidence(fullTextAnnotation.pages ?? undefined),
language: result.textAnnotations?.[0]?.locale || undefined,
blocks: fullTextAnnotation.pages?.[0]?.blocks?.map(block => ({
text: this.extractBlockText(block),
confidence: block.confidence || 0,
boundingBox: block.boundingBox
})) || []
};
if (this.config.costOptimization.enableCaching) {
await this.cache.set(cacheKey, extractionResult, 3600);
}
return extractionResult;
} catch (error) {
return this.errorHandler.handleWithFallback(error, () => this.fallbackTextExtraction(), {
service: 'Vision',
operation: 'extractText'
});
}
}
async analyzeImage(imageBuffer) {
const cacheKey = `vision:analysis:${this.hashBuffer(imageBuffer)}`;
if (this.config.costOptimization.enableCaching) {
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
}
try {
if (!this.client) {
return this.fallbackImageAnalysis();
}
const [result] = await this.client.annotateImage({
image: {
content: imageBuffer.toString('base64')
},
features: [{
type: 'LABEL_DETECTION',
maxResults: 10
}, {
type: 'SAFE_SEARCH_DETECTION'
}, {
type: 'IMAGE_PROPERTIES'
}, {
type: 'CROP_HINTS',
maxResults: 3
}]
});
const analysisResult = {
labels: result.labelAnnotations?.map(label => ({
description: label.description || '',
score: label.score || 0
})) || [],
safeSearch: {
adult: String(result.safeSearchAnnotation?.adult ?? 'UNKNOWN'),
violence: String(result.safeSearchAnnotation?.violence ?? 'UNKNOWN'),
medical: String(result.safeSearchAnnotation?.medical ?? 'UNKNOWN')
},
colors: result.imagePropertiesAnnotation?.dominantColors?.colors?.map(color => ({
color: {
red: color.color?.red || 0,
green: color.color?.green || 0,
blue: color.color?.blue || 0
},
score: color.score || 0,
pixelFraction: color.pixelFraction || 0
})) || [],
cropHints: result.cropHintsAnnotation?.cropHints?.map(hint => ({
boundingBox: hint.boundingPoly,
confidence: hint.confidence || 0,
importanceFraction: hint.importanceFraction || 0
}))
};
if (this.config.costOptimization.enableCaching) {
await this.cache.set(cacheKey, analysisResult, 3600);
}
return analysisResult;
} catch (error) {
return this.errorHandler.handleWithFallback(error, () => this.fallbackImageAnalysis(), {
service: 'Vision',
operation: 'analyzeImage'
});
}
}
async removeBackground(imageBuffer) {
const cacheKey = `vision:removebg:${this.hashBuffer(imageBuffer)}`;
if (this.config.costOptimization.enableCaching) {
const cached = await this.cache.get(cacheKey);
if (cached) return Buffer.from(cached, 'base64');
}
try {
const formData = new FormData();
// Convert Node Buffer to Blob-compatible data for fetch
formData.append('image_file', new Blob([new Uint8Array(imageBuffer)]), 'image.jpg');
formData.append('size', 'auto');
const response = await fetch('https://api.remove.bg/v1.0/removebg', {
method: 'POST',
headers: {
'X-Api-Key': this.removeBgApiKey
},
body: formData
});
if (!response.ok) {
throw new Error(`Remove.bg API error: ${response.status}`);
}
const resultBuffer = Buffer.from(await response.arrayBuffer());
if (this.config.costOptimization.enableCaching) {
await this.cache.set(cacheKey, resultBuffer.toString('base64'), 3600);
}
return resultBuffer;
} catch (error) {
return this.errorHandler.handleWithFallback(error, () => imageBuffer, {
service: 'Vision',
operation: 'removeBackground'
});
}
}
async generateImageDescription(imageBuffer) {
try {
const analysis = await this.analyzeImage(imageBuffer);
const topLabels = analysis.labels.slice(0, 5);
if (topLabels.length === 0) {
return 'No description available';
}
const labelDescriptions = topLabels.map(l => l.description).join(', ');
return `This image contains: ${labelDescriptions}`;
} catch (error) {
return this.errorHandler.handleWithFallback(error, () => 'Unable to generate image description', {
service: 'Vision',
operation: 'generateImageDescription'
});
}
}
convertBoundingPoly(boundingPoly) {
if (!boundingPoly?.vertices || boundingPoly.vertices.length < 4) {
return {
left: 0,
top: 0,
width: 0,
height: 0
};
}
const vertices = boundingPoly.vertices;
const left = Math.min(...vertices.map(v => v.x || 0));
const top = Math.min(...vertices.map(v => v.y || 0));
const right = Math.max(...vertices.map(v => v.x || 0));
const bottom = Math.max(...vertices.map(v => v.y || 0));
return {
left,
top,
width: right - left,
height: bottom - top
};
}
likelihoodToScore(likelihood) {
const map = {
VERY_UNLIKELY: 0,
UNLIKELY: 0.25,
POSSIBLE: 0.5,
LIKELY: 0.75,
VERY_LIKELY: 1
};
return map[likelihood || ''] || 0;
}
calculateAverageConfidence(pages) {
if (!pages || pages.length === 0) return 0;
const confidences = [];
pages.forEach(page => {
page.blocks?.forEach(block => {
if (block.confidence) confidences.push(block.confidence);
});
});
if (confidences.length === 0) return 0;
return confidences.reduce((a, b) => a + b, 0) / confidences.length;
}
extractBlockText(block) {
if (!block.paragraphs) return '';
return block.paragraphs.map(p => p.words?.map(w => w.symbols?.map(s => s.text || '').join('') || '').join(' ') || '').join('\n');
}
hashBuffer(buffer) {
const crypto = require('crypto');
return crypto.createHash('md5').update(buffer).digest('hex');
}
fallbackFaceDetection() {
return [{
boundingBox: {
left: 100,
top: 100,
width: 200,
height: 200
},
confidence: 0.5,
emotions: {
joy: 0.5,
sorrow: 0,
anger: 0,
surprise: 0
}
}];
}
fallbackObjectDetection() {
return [{
name: 'object',
confidence: 0.5,
boundingBox: {
left: 0,
top: 0,
width: 100,
height: 100
}
}];
}
fallbackTextExtraction() {
return {
text: 'Text extraction unavailable',
confidence: 0,
blocks: []
};
}
fallbackImageAnalysis() {
return {
labels: [{
description: 'image',
score: 0.5
}],
safeSearch: {
adult: 'UNKNOWN',
violence: 'UNKNOWN',
medical: 'UNKNOWN'
},
colors: []
};
}
}
export { VisionService };
//# sourceMappingURL=vision-service.js.map