Skip to main content
Glama
brandfolder-enhanced.js11.2 kB
// api/brandfolder-enhanced.js - Production-ready Brandfolder OAuth integration // Replaces mock results with live Brandfolder API when OAuth approved export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } try { const { intent, test_mode = false } = req.body; // Check OAuth status const oauthStatus = await checkBrandfolderOAuth(); if (!oauthStatus.authenticated && !test_mode) { return res.status(401).json({ success: false, error: 'brandfolder_oauth_pending', message: 'Brandfolder OAuth credentials pending approval', intelligence_demo: await generateIntelligentDemo(intent) }); } // Live Brandfolder integration const results = await searchBrandfolderAssets(intent, oauthStatus.token); res.status(200).json({ success: true, source: 'live_brandfolder', intent, results, oauth_status: 'authenticated' }); } catch (error) { console.error('Brandfolder integration error:', error); // Fallback to intelligent demo on any OAuth issues const fallbackResults = await generateIntelligentDemo(req.body.intent); res.status(200).json({ success: true, source: 'intelligent_fallback', message: 'Using intelligent demo due to OAuth issue', results: fallbackResults, oauth_error: error.message }); } } async function checkBrandfolderOAuth() { const clientId = process.env.BRANDFOLDER_CLIENT_ID; const clientSecret = process.env.BRANDFOLDER_CLIENT_SECRET; if (!clientId || !clientSecret) { return { authenticated: false, reason: 'missing_credentials' }; } try { // Test OAuth credentials with Brandfolder API const tokenResponse = await fetch('https://api.brandfolder.com/v4/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'client_credentials', client_id: clientId, client_secret: clientSecret }) }); if (!tokenResponse.ok) { return { authenticated: false, reason: 'invalid_credentials' }; } const tokenData = await tokenResponse.json(); return { authenticated: true, token: tokenData.access_token, expires_at: Date.now() + (tokenData.expires_in * 1000) }; } catch (error) { return { authenticated: false, reason: 'api_error', error: error.message }; } } async function searchBrandfolderAssets(intent, accessToken) { // Enhanced search using your Phase 2 intelligence + live Brandfolder API const searchParams = buildBrandfolderSearchParams(intent); try { const response = await fetch( `https://api.brandfolder.com/v4/brandfolders/${process.env.BREVILLE_BRANDFOLDER_ID}/search`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify(searchParams) } ); if (!response.ok) { throw new Error(`Brandfolder API error: ${response.status}`); } const data = await response.json(); // Process results using your Phase 2 intelligence return processLiveBrandfolderResults(data, intent); } catch (error) { throw new Error(`Live search failed: ${error.message}`); } } function buildBrandfolderSearchParams(intent) { // Convert your Phase 2 intelligence into Brandfolder API search parameters const params = { query: '', filters: { tags: [], sections: [], file_types: [] }, sort: 'relevance', limit: 20 }; // Add product-specific search terms if (intent.products?.length > 0) { const productTerms = intent.products.map(p => [p.name, p.model]).flat(); params.query = productTerms.join(' OR '); } // Map your 14 Vault sections to Brandfolder sections if (intent.section) { params.filters.sections = mapVaultSectionToBrandfolder(intent.section); } // Add format filters based on use case if (intent.formats?.length > 0) { params.filters.file_types = intent.formats.map(f => f.toLowerCase()); } // Regional filtering for brand-specific assets if (intent.region && intent.brand) { params.filters.tags.push(`${intent.brand.toLowerCase()}_${intent.region.toLowerCase()}`); } // Business context tags if (intent.use_case) { params.filters.tags.push(`use_case_${intent.use_case}`); } return params; } function mapVaultSectionToBrandfolder(vaultSection) { // Map your 14 official Vault sections to actual Brandfolder section IDs const sectionMapping = { "Product Photography": ["product_photos", "hero_images"], "Lifestyle Photography": ["lifestyle", "in_use_photos"], "Logos": ["brand_logos", "product_logos"], "Digital Assets (incl. Websites, Programmatic & EDM)": ["digital_assets", "web_banners", "edm"], "Social (incl. Videos, Statics, Stories & Keynotes)": ["social_media", "video_content"], "Point of Sale": ["pos_materials", "retail_displays"], "YouTube Videos": ["video_content", "tutorials"], // Add remaining 7 sections... }; return sectionMapping[vaultSection] || []; } function processLiveBrandfolderResults(data, intent) { // Process live Brandfolder results using your Phase 2 intelligence const processedResults = data.assets?.map(asset => ({ id: asset.id, name: asset.name, description: asset.description, url: asset.url, download_url: asset.download_url, thumbnail_url: asset.thumbnail_url, file_type: asset.file_type, file_size: asset.file_size, dimensions: asset.dimensions, created_at: asset.created_at, tags: asset.tags, // Enhanced with your intelligence confidence: calculateAssetConfidence(asset, intent), intelligence_match: analyzeIntelligenceMatch(asset, intent), usage_recommendations: generateUsageRecommendations(asset, intent), regional_suitability: assessRegionalSuitability(asset, intent) })) || []; // Sort by confidence (highest first) processedResults.sort((a, b) => b.confidence - a.confidence); return { total_found: data.total_count || 0, assets: processedResults, search_metadata: { brandfolder_query: data.query_used, processing_time_ms: data.processing_time, intelligence_enhanced: true }, suggestions: generateLiveSearchSuggestions(processedResults, intent) }; } function calculateAssetConfidence(asset, intent) { let confidence = 0.5; // Base confidence // Product match scoring if (intent.products?.length > 0) { const productMatches = intent.products.some(product => asset.name?.toLowerCase().includes(product.name?.toLowerCase()) || asset.name?.toLowerCase().includes(product.model?.toLowerCase()) || asset.tags?.some(tag => tag.toLowerCase().includes(product.name?.toLowerCase()) || tag.toLowerCase().includes(product.model?.toLowerCase()) ) ); if (productMatches) confidence += 0.3; } // Format match scoring if (intent.formats?.length > 0) { const formatMatch = intent.formats.some(format => asset.file_type?.toLowerCase() === format.toLowerCase() ); if (formatMatch) confidence += 0.15; } // Use case relevance scoring if (intent.use_case) { const useCaseRelevant = asset.tags?.some(tag => tag.toLowerCase().includes(intent.use_case?.toLowerCase()) ); if (useCaseRelevant) confidence += 0.1; } // Regional/brand relevance if (intent.brand) { const brandMatch = asset.tags?.some(tag => tag.toLowerCase().includes(intent.brand?.toLowerCase()) ); if (brandMatch) confidence += 0.1; } return Math.min(confidence, 1.0); } function analyzeIntelligenceMatch(asset, intent) { return { product_match: intent.products?.some(p => asset.name?.toLowerCase().includes(p.name?.toLowerCase()) ) || false, section_match: true, // Determined by search filters format_optimized: intent.formats?.includes(asset.file_type) || false, use_case_aligned: asset.tags?.some(tag => tag.toLowerCase().includes(intent.use_case?.toLowerCase()) ) || false, regional_appropriate: intent.brand ? asset.tags?.some(tag => tag.toLowerCase().includes(intent.brand?.toLowerCase())) : true }; } function generateUsageRecommendations(asset, intent) { const recommendations = []; // Format-specific recommendations if (asset.file_type?.toLowerCase() === 'png' && intent.use_case === 'presentation') { recommendations.push("✅ PNG format perfect for presentations with transparency support"); } if (asset.file_type?.toLowerCase() === 'svg' && intent.use_case === 'web') { recommendations.push("✅ SVG format ideal for web use - infinite scalability"); } // Dimension recommendations if (asset.dimensions && intent.use_case === 'social_media') { recommendations.push(`📐 Dimensions: ${asset.dimensions} - verify platform requirements`); } // File size considerations if (asset.file_size > 5000000 && intent.use_case === 'web') { // 5MB recommendations.push("⚠️ Large file size - consider optimizing for web use"); } return recommendations; } function assessRegionalSuitability(asset, intent) { if (!intent.region || !intent.brand) { return { suitable: true, reason: "No regional restrictions" }; } // Check if asset has appropriate branding for region const hasCorrectBranding = asset.tags?.some(tag => tag.toLowerCase().includes(intent.brand?.toLowerCase()) ); return { suitable: hasCorrectBranding, reason: hasCorrectBranding ? `✅ Appropriate ${intent.brand} branding for ${intent.region}` : `⚠️ May not have correct ${intent.brand} branding for ${intent.region}` }; } function generateLiveSearchSuggestions(results, intent) { const suggestions = []; if (results.length === 0) { suggestions.push("Try broader search terms or check product name spelling"); suggestions.push("Consider searching by model number instead of product name"); } else if (results.length > 0 && results[0].confidence < 0.8) { suggestions.push("Consider refining your search with more specific terms"); suggestions.push("Try adding format or use case specifications"); } // Format suggestions based on use case if (intent.use_case === 'presentation' && !intent.formats?.includes('PNG')) { suggestions.push("💡 Consider PNG format for presentations (transparency support)"); } if (intent.use_case === 'web' && !intent.formats?.includes('SVG')) { suggestions.push("💡 Consider SVG format for web use (scalable)"); } return suggestions; } async function generateIntelligentDemo(intent) { // Your existing Phase 2 intelligent demo logic as fallback // This ensures system never fails even with OAuth issues return { demo_mode: true, intelligence_level: "phase_2_enhanced", note: "Live Brandfolder integration pending OAuth approval" }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vnsavitri/dam-butler-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server