Skip to main content
Glama
character-pipeline.jsβ€’40.7 kB
import { AffogatoClient } from './affogato-client.js'; import { Client } from '@notionhq/client'; import { CharacterConsistencyManager } from './character-consistency.js'; import { DatabaseSchemaEnhancer } from './database-schema-enhancer.js'; import { ClaudeCharacterAnalyzer } from './claude-analyzer.js'; import fs from 'fs'; import path from 'path'; export class CharacterProductionPipeline { constructor(affogatoApiKey) { this.affogato = new AffogatoClient(affogatoApiKey); this.notion = null; // Will be initialized when needed this.outputPath = './generated_assets'; this.consistencyManager = new CharacterConsistencyManager(); this.dbEnhancer = null; // Will be initialized with notion client this.claudeAnalyzer = new ClaudeCharacterAnalyzer(); } async getNotionClient() { if (this.notion) return this.notion; // Get Notion connection let connectionSettings; const hostname = process.env.REPLIT_CONNECTORS_HOSTNAME; const xReplitToken = process.env.REPL_IDENTITY ? 'repl ' + process.env.REPL_IDENTITY : process.env.WEB_REPL_RENEWAL ? 'depl ' + process.env.WEB_REPL_RENEWAL : null; if (!xReplitToken) { throw new Error('X_REPLIT_TOKEN not found for repl/depl'); } connectionSettings = await fetch( 'https://' + hostname + '/api/v2/connection?include_secrets=true&connector_names=notion', { headers: { 'Accept': 'application/json', 'X_REPLIT_TOKEN': xReplitToken } } ).then(res => res.json()).then(data => data.items?.[0]); const accessToken = connectionSettings?.settings?.access_token || connectionSettings.settings?.oauth?.credentials?.access_token; if (!connectionSettings || !accessToken) { throw new Error('Notion not connected'); } this.notion = new Client({ auth: accessToken }); this.dbEnhancer = new DatabaseSchemaEnhancer(this.notion); return this.notion; } // Store character in PostgreSQL for storyline integration async storeCharacterInPostgreSQL(name, description, affogatoId, notionPageId, traits = {}) { try { const { Pool } = await import('pg'); const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const query = ` INSERT INTO characters (name, description, affogato_id, notion_page_id, personality_traits, physical_description, backstory, voice_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (name) DO UPDATE SET description = $2, affogato_id = $3, notion_page_id = $4, personality_traits = $5, physical_description = $6, backstory = $7, voice_id = $8, updated_at = CURRENT_TIMESTAMP RETURNING id `; const personalityTraits = traits.personalityTraits || []; const physicalDescription = `${traits.primaryColor || ''} ${traits.size || ''} ${traits.construction || ''} puppet with ${traits.eyeType || ''} eyes`.trim(); const backstory = traits.backstory || description; const result = await pool.query(query, [ name, description, affogatoId, notionPageId, JSON.stringify(personalityTraits), physicalDescription, backstory, affogatoId // Using affogato ID as voice reference for now ]); await pool.end(); console.log(`βœ… Character "${name}" stored in PostgreSQL with ID ${result.rows[0].id}`); return result.rows[0].id; } catch (error) { console.error('❌ Failed to store character in PostgreSQL:', error.message); // Don't throw - this is supplementary to the main workflow return null; } } // Complete character creation workflow with enhanced consistency async createCharacterFromDescription(characterData) { console.log('🎭 STARTING ENHANCED CHARACTER PRODUCTION PIPELINE'); console.log('=====================================================\n'); const { name, description, characterType = 'Main Character', referenceImagePath, personalityTraits = [], voiceDescription = '', characterTraits = {} // New: comprehensive character traits } = characterData; console.log(`Creating character: ${name}`); console.log(`Description: ${description}`); try { // Step 1: Upload reference image to Affogato console.log('\nπŸ“€ Step 1: Uploading reference image...'); const uploadedAsset = await this.affogato.uploadAsset(referenceImagePath); // Step 2: Create character in Affogato console.log('\n🎭 Step 2: Creating character in Affogato...'); const affogatoCharacter = await this.affogato.createCharacter( uploadedAsset.id, name, 'Custom', description ); // Step 3: Generate puppet images with enhanced consistency console.log('\n🎨 Step 3: Generating puppet images with consistency controls...'); // Set character traits in the Affogato client for consistent generation this.affogato.setCharacterTraits(characterTraits); const puppetImages = await this.affogato.generatePuppetImages(affogatoCharacter.id, undefined, characterTraits); // Step 4: Create character in Notion database console.log('\nπŸ“ Step 4: Adding to Notion database...'); const notion = await this.getNotionClient(); // Create enhanced character record with comprehensive traits const enhancedProperties = await this.dbEnhancer.createCharacterWithTraits( { name, description, characterType }, characterTraits ); // Add additional standard fields enhancedProperties['Personality Traits'] = { multi_select: personalityTraits.map(trait => ({ name: trait })) }; enhancedProperties['Voice ID'] = { rich_text: [{ text: { content: affogatoCharacter.id } }] }; enhancedProperties['First Appearance'] = { date: { start: new Date().toISOString().split('T')[0] } }; const notionCharacter = await notion.pages.create({ parent: { database_id: process.env.CHARACTERS_MASTER_DB || '5b96c172-96b2-4b53-a52c-60d4874779f4' }, properties: enhancedProperties }); // Also store character in PostgreSQL for storyline integration await this.storeCharacterInPostgreSQL(name, description, affogatoCharacter.id, notionCharacter.id, characterTraits); // Step 5: Wait for puppet images to complete and download console.log('\n⏳ Step 5: Waiting for puppet images to complete...'); await this.affogato.ensureOutputDirectory(this.outputPath); const completedAssets = []; for (const image of puppetImages) { if (image.status !== 'failed') { // Poll for completion let retries = 30; // Max 5 minutes while (retries > 0) { try { const status = await this.affogato.getGenerationStatus(image.generation_id); if (status.status === 'completed' && status.media[0]?.url) { const imageType = image.type === 'emotion' ? image.emotion : `${image.view}_view`; const safeName = (name && typeof name === 'string' ? String(name) : 'character').toLowerCase().replace(/\s+/g, '_'); const filename = `${safeName}_${imageType}.png`; const filepath = path.join(this.outputPath, filename); await this.affogato.downloadMedia(status.media[0].url, filepath); completedAssets.push({ type: image.type, emotion: image.emotion, view: image.view, filepath: filepath, url: status.media[0].url, media_id: status.media[0].id }); const displayName = image.type === 'emotion' ? `${image.emotion} emotion` : `${image.view} view`; console.log(` βœ… Downloaded ${displayName} puppet image`); break; } else if (status.status === 'failed') { const displayName = image.type === 'emotion' ? `${image.emotion} emotion` : `${image.view} view`; console.log(` ❌ ${displayName} generation failed`); break; } } catch (error) { const displayName = image.type === 'emotion' ? `${image.emotion} emotion` : `${image.view} view`; console.log(` ⚠️ Status check failed for ${displayName}: ${error.message}`); } await this.affogato.delay(10000); // Wait 10 seconds retries--; } } } // Step 6: Add image assets to Notion console.log('\nπŸ“Š Step 6: Adding image assets to Notion...'); for (const asset of completedAssets) { const assetName = asset.type === 'emotion' ? `${name} - ${asset.emotion} emotion` : `${name} - ${asset.view} view`; const imageType = asset.type === 'emotion' ? `Puppet - ${asset.emotion.charAt(0).toUpperCase() + asset.emotion.slice(1)}` : `Puppet - ${asset.view.charAt(0).toUpperCase() + asset.view.slice(1)} View`; const angle = asset.type === 'emotion' ? 'Front View' : `${asset.view.charAt(0).toUpperCase() + asset.view.slice(1)} View`; await notion.pages.create({ parent: { database_id: process.env.IMAGE_ASSETS_DB || '33335cfb-1cb4-4bb0-88ba-b5188456749d' }, properties: { 'Asset Name': { title: [{ text: { content: assetName } }] }, 'Character': { relation: [{ id: notionCharacter.id }] }, 'Image Type': { select: { name: imageType } }, 'Angle': { select: { name: angle } }, 'Usage Count': { number: 0 }, 'Created Date': { date: { start: new Date().toISOString().split('T')[0] } } } }); } console.log('\nπŸŽ‰ CHARACTER PRODUCTION COMPLETE!'); console.log('================================='); console.log(`βœ… Character "${name}" created successfully`); console.log(`πŸ“Š Affogato Character ID: ${affogatoCharacter.id}`); console.log(`πŸ“ Notion Character ID: ${notionCharacter.id}`); console.log(`🎨 Generated ${completedAssets.length} puppet images`); console.log(`πŸ“ Assets saved to: ${this.outputPath}`); return { success: true, character: { name: name, affogatoId: affogatoCharacter.id, notionId: notionCharacter.id, description: description, traits: characterTraits, assets: completedAssets }, message: `Character "${name}" created with ${completedAssets.length} puppet images using consistency system` }; } catch (error) { console.error(`❌ Character production failed: ${error.message}`); return { success: false, error: error.message, character: { name: name } }; } } // Generate scenes/videos for existing character async generateCharacterScene(characterId, sceneData) { console.log(`🎬 GENERATING SCENE FOR CHARACTER: ${characterId}`); console.log('===============================================\n'); const { sceneTitle, sceneDescription, duration = 5, audioFile = null, generateVideo = true } = sceneData; try { // Step 1: Generate scene image console.log('🎨 Step 1: Generating scene image...'); const generationData = { aspect_ratio: '16:9', character: { character_id: characterId, mode: 'balanced' }, prompt: { positive: `${sceneDescription}, cinematic scene, high quality, professional lighting, 4K`, negative: 'nsfw, deformed, extra limbs, bad anatomy, text, worst quality, jpeg artifacts, ugly' }, quality: 'Plus', steps: 25, cfg_scale: 8 }; const imageGeneration = await this.affogato.makeApiRequest('/pub/v1/generations', [generationData]); // Step 2: Wait for image completion console.log('⏳ Step 2: Waiting for scene image to complete...'); let imageCompleted = false; let imageUrl = null; let mediaId = null; let retries = 30; while (retries > 0 && !imageCompleted) { const status = await this.affogato.getGenerationStatus(imageGeneration.generation_id); if (status.status === 'completed' && status.media[0]?.url) { imageUrl = status.media[0].url; mediaId = status.media[0].id; imageCompleted = true; console.log('βœ… Scene image completed'); break; } else if (status.status === 'failed') { throw new Error('Scene image generation failed'); } await this.affogato.delay(10000); retries--; } if (!imageCompleted) { throw new Error('Scene image generation timed out'); } const results = { sceneTitle: sceneTitle, sceneImage: { url: imageUrl, media_id: mediaId } }; // Step 3: Generate video if requested if (generateVideo) { console.log('πŸŽ₯ Step 3: Converting to video...'); let videoGeneration; if (audioFile) { // Upload audio and create lipsync video const audioAsset = await this.affogato.uploadAsset(audioFile); videoGeneration = await this.affogato.generateLipsyncVideo(characterId, audioAsset.id, sceneDescription); } else { // Create video from image videoGeneration = await this.affogato.generateVideoFromImage(mediaId, sceneDescription, duration); } // Wait for video completion console.log('⏳ Waiting for video to complete...'); let videoRetries = 60; // Videos take longer while (videoRetries > 0) { const videoStatus = await this.affogato.getGenerationStatus(videoGeneration.generation_id); if (videoStatus.status === 'completed' && videoStatus.media[0]?.url) { results.video = { url: videoStatus.media[0].url, media_id: videoStatus.media[0].id }; console.log('βœ… Video completed'); break; } else if (videoStatus.status === 'failed') { console.log('⚠️ Video generation failed'); break; } await this.affogato.delay(15000); // 15 seconds for video videoRetries--; } } // Step 4: Save to local files console.log('πŸ’Ύ Step 4: Downloading assets...'); await this.affogato.ensureOutputDirectory(this.outputPath); const sceneFilename = `scene_${(sceneTitle || 'scene').toLowerCase().replace(/\s+/g, '_')}.jpg`; const scenePath = path.join(this.outputPath, sceneFilename); await this.affogato.downloadMedia(imageUrl, scenePath); results.sceneImage.filepath = scenePath; if (results.video) { const videoFilename = `scene_${(sceneTitle || 'scene').toLowerCase().replace(/\s+/g, '_')}.mp4`; const videoPath = path.join(this.outputPath, videoFilename); await this.affogato.downloadMedia(results.video.url, videoPath); results.video.filepath = videoPath; } console.log('\n🎬 SCENE GENERATION COMPLETE!'); console.log(`βœ… Scene "${sceneTitle}" created successfully`); console.log(`πŸ“ Assets saved to: ${this.outputPath}`); return { success: true, scene: results }; } catch (error) { console.error(`❌ Scene generation failed: ${error.message}`); return { success: false, error: error.message }; } } // Get character from Notion and create scene with consistency async createSceneFromNotion(characterName, sceneData) { console.log(`πŸ” Finding character "${characterName}" in Notion with traits...`); try { const notion = await this.getNotionClient(); // Search for character in Notion database const response = await notion.databases.query({ database_id: process.env.CHARACTERS_MASTER_DB || '5b96c172-96b2-4b53-a52c-60d4874779f4', filter: { property: 'Character Name', title: { equals: characterName } } }); if (response.results.length === 0) { throw new Error(`Character "${characterName}" not found in database`); } const characterPage = response.results[0]; const affogatoId = characterPage.properties['Voice ID']?.rich_text[0]?.text?.content; if (!affogatoId) { throw new Error(`No Affogato character ID found for "${characterName}"`); } // Extract character traits from Notion const characterTraits = this.dbEnhancer.extractCharacterTraits(characterPage); console.log(`βœ… Found character with traits: size=${characterTraits.size}, style=${characterTraits.movementStyle}`); // Generate scene with character consistency return await this.generateCharacterSceneWithConsistency(affogatoId, sceneData, characterTraits); } catch (error) { console.error(`❌ Scene creation from Notion failed: ${error.message}`); return { success: false, error: error.message }; } } // Generate scene maintaining character consistency async generateCharacterSceneWithConsistency(characterId, sceneData, characterTraits) { console.log(`🎬 GENERATING CONSISTENT SCENE FOR CHARACTER: ${characterId}`); console.log('=======================================================\n'); const { sceneTitle, sceneDescription, characterAction = 'standing naturally', characterEmotion = 'neutral', scenePosition = 'center frame', lightingSetup = 'studio_standard', duration = 5, audioFile = null, generateVideo = true } = sceneData; try { // Set character traits in Affogato client this.affogato.setCharacterTraits(characterTraits); // Build consistent scene prompt const scenePrompt = this.consistencyManager.buildScenePrompt( [{ name: characterTraits.name, traits: characterTraits }], sceneDescription, { 'CHARACTER_ACTION': characterAction, 'EMOTION_STATE': characterEmotion, 'SCENE_POSITION': scenePosition, 'LIGHTING_SETUP': lightingSetup } ); console.log('🎨 Step 1: Generating scene with character consistency...'); const generationData = { aspect_ratio: '16:9', character: { character_id: characterId, mode: 'balanced' }, prompt: { positive: `${scenePrompt}, ${this.consistencyManager.templatePrompts.consistencyRules.mandatoryElements.join(', ')}`, negative: this.consistencyManager.templatePrompts.consistencyRules.prohibitedElements.join(', ') }, quality: 'Plus', steps: 30, cfg_scale: 9 }; const imageGeneration = await this.affogato.makeApiRequest('/pub/v1/generations', [generationData]); // Wait for completion and handle video generation same as before console.log('⏳ Step 2: Waiting for scene image to complete...'); let imageCompleted = false; let imageUrl = null; let mediaId = null; let retries = 30; while (retries > 0 && !imageCompleted) { const status = await this.affogato.getGenerationStatus(imageGeneration.generation_id); if (status.status === 'completed' && status.media[0]?.url) { imageUrl = status.media[0].url; mediaId = status.media[0].id; imageCompleted = true; console.log('βœ… Consistent scene image completed'); break; } else if (status.status === 'failed') { throw new Error('Scene image generation failed'); } await this.affogato.delay(10000); retries--; } if (!imageCompleted) { throw new Error('Scene image generation timed out'); } // Update character usage tracking await this.dbEnhancer.updateCharacterUsage(characterId); const results = { sceneTitle: sceneTitle, sceneImage: { url: imageUrl, media_id: mediaId }, characterConsistency: { traitsUsed: characterTraits, promptValidation: this.consistencyManager.validatePrompt(scenePrompt) } }; // Handle video generation if requested (same as before) if (generateVideo) { console.log('πŸŽ₯ Step 3: Converting to video with character consistency...'); let videoGeneration; if (audioFile) { const audioAsset = await this.affogato.uploadAsset(audioFile); videoGeneration = await this.affogato.generateLipsyncVideo(characterId, audioAsset.id, sceneDescription); } else { videoGeneration = await this.affogato.generateVideoFromImage(mediaId, sceneDescription, duration); } // Wait for video completion console.log('⏳ Waiting for video to complete...'); let videoRetries = 60; while (videoRetries > 0) { const videoStatus = await this.affogato.getGenerationStatus(videoGeneration.generation_id); if (videoStatus.status === 'completed' && videoStatus.media[0]?.url) { results.video = { url: videoStatus.media[0].url, media_id: videoStatus.media[0].id }; console.log('βœ… Consistent video completed'); break; } else if (videoStatus.status === 'failed') { console.log('⚠️ Video generation failed'); break; } await this.affogato.delay(15000); videoRetries--; } } // Save assets console.log('πŸ’Ύ Step 4: Downloading consistent assets...'); await this.affogato.ensureOutputDirectory(this.outputPath); const sceneFilename = `scene_${(sceneTitle || 'scene').toLowerCase().replace(/\s+/g, '_')}_consistent.jpg`; const scenePath = path.join(this.outputPath, sceneFilename); await this.affogato.downloadMedia(imageUrl, scenePath); results.sceneImage.filepath = scenePath; if (results.video) { const videoFilename = `scene_${(sceneTitle || 'scene').toLowerCase().replace(/\s+/g, '_')}_consistent.mp4`; const videoPath = path.join(this.outputPath, videoFilename); await this.affogato.downloadMedia(results.video.url, videoPath); results.video.filepath = videoPath; } console.log('\n🎬 CONSISTENT SCENE GENERATION COMPLETE!'); console.log(`βœ… Scene "${sceneTitle}" created with character consistency`); console.log(`πŸ“Š Consistency validation: ${results.characterConsistency.promptValidation.isValid ? 'PASSED' : 'ISSUES FOUND'}`); console.log(`πŸ“ Assets saved to: ${this.outputPath}`); return { success: true, scene: results }; } catch (error) { console.error(`❌ Consistent scene generation failed: ${error.message}`); return { success: false, error: error.message }; } } // Retrieve character traits from Notion database for consistency async getCharacterTraitsFromNotion(characterName) { try { const notion = await this.getNotionClient(); const response = await notion.databases.query({ database_id: process.env.CHARACTERS_MASTER_DB || '5b96c172-96b2-4b53-a52c-60d4874779f4', filter: { property: 'Character Name', title: { equals: characterName } } }); if (response.results.length === 0) { throw new Error(`Character "${characterName}" not found in database`); } const characterPage = response.results[0]; return { page: characterPage, traits: this.dbEnhancer.extractCharacterTraits(characterPage), affogatoId: characterPage.properties['Voice ID']?.rich_text[0]?.text?.content }; } catch (error) { console.error(`❌ Failed to retrieve character traits: ${error.message}`); throw error; } } // Generate scenes using template system with character consistency async generateScene(characterName, sceneData, directionalOverrides = {}) { console.log(`🎬 GENERATING SCENE WITH CHARACTER CONSISTENCY`); console.log('===============================================\n'); try { // Retrieve character traits from database const characterData = await this.getCharacterTraitsFromNotion(characterName); console.log(`βœ… Retrieved character traits for ${characterName}`); // Merge traits with directional overrides const effectiveTraits = { ...characterData.traits, ...directionalOverrides }; // Generate scene using consistency system return await this.generateCharacterSceneWithConsistency( characterData.affogatoId, sceneData, effectiveTraits ); } catch (error) { console.error(`❌ Scene generation failed: ${error.message}`); return { success: false, error: error.message }; } } // Generate multi-character scenes with consistency async generateMultiCharacterScene(characterNames, sceneData) { console.log(`🎭 GENERATING MULTI-CHARACTER SCENE`); console.log('====================================\n'); try { // Retrieve all character traits const characters = []; for (const name of characterNames) { const characterData = await this.getCharacterTraitsFromNotion(name); characters.push({ name: name, traits: characterData.traits, affogatoId: characterData.affogatoId }); } console.log(`βœ… Retrieved traits for ${characters.length} characters`); // Build multi-character scene prompt const scenePrompt = this.consistencyManager.buildScenePrompt( characters, sceneData.sceneDescription, { 'CHARACTER_INTERACTIONS': sceneData.characterInteractions || 'standing together', 'LIGHTING_SETUP': sceneData.lightingSetup || 'studio_standard' } ); console.log('🎨 Generating multi-character scene...'); const generationData = { aspect_ratio: '16:9', prompt: { positive: `${scenePrompt}, ${this.consistencyManager.templatePrompts.consistencyRules.mandatoryElements.join(', ')}`, negative: this.consistencyManager.templatePrompts.consistencyRules.prohibitedElements.join(', ') }, quality: 'Plus', steps: 30, cfg_scale: 9 }; const imageGeneration = await this.affogato.makeApiRequest('/pub/v1/generations', [generationData]); // Wait for completion and save console.log('⏳ Waiting for multi-character scene to complete...'); let retries = 30; while (retries > 0) { const status = await this.affogato.getGenerationStatus(imageGeneration.generation_id); if (status.status === 'completed' && status.media[0]?.url) { const sceneFilename = `multi_scene_${(sceneData.sceneTitle || 'scene').toLowerCase().replace(/\s+/g, '_')}.jpg`; const scenePath = path.join(this.outputPath, sceneFilename); await this.affogato.downloadMedia(status.media[0].url, scenePath); console.log('βœ… Multi-character scene completed'); return { success: true, scene: { title: sceneData.sceneTitle, characters: characters.map(c => c.name), filepath: scenePath, url: status.media[0].url, consistency: { promptValidation: this.consistencyManager.validatePrompt(scenePrompt) } } }; } else if (status.status === 'failed') { throw new Error('Multi-character scene generation failed'); } await this.affogato.delay(10000); retries--; } throw new Error('Multi-character scene generation timed out'); } catch (error) { console.error(`❌ Multi-character scene generation failed: ${error.message}`); return { success: false, error: error.message }; } } // Create character from description with Claude intelligence async createCharacterFromDescription(description, characterName, culturalBackground = null) { console.log(`🎭 CREATING CHARACTER FROM DESCRIPTION`); console.log('======================================\n'); try { // Initialize output directory const characterPath = path.join(this.outputPath, (characterName || 'character').toLowerCase().replace(/\s+/g, '_')); if (!fs.existsSync(characterPath)) { fs.mkdirSync(characterPath, { recursive: true }); } // Step 1: Use Claude to analyze character description console.log('🧠 Analyzing character with Claude AI...'); const claudeAnalysis = await this.claudeAnalyzer.analyzeCharacterDescription( description, culturalBackground ); if (!claudeAnalysis.success) { throw new Error(`Claude analysis failed: ${claudeAnalysis.error}`); } // Step 2: Generate optimized prompts with Claude console.log('🎨 Generating optimized prompts...'); const promptGeneration = await this.claudeAnalyzer.generateOptimizedPrompts( claudeAnalysis.traits ); if (!promptGeneration.success) { throw new Error(`Prompt generation failed: ${promptGeneration.error}`); } // Step 3: Set character traits for consistency await this.affogato.setCharacterTraits(claudeAnalysis.traits); // Step 4: Generate character images using optimized prompts console.log('🎭 Generating character images...'); const imageGeneration = await this.affogato.generatePuppetImages( characterName, promptGeneration.prompts.character_consistency_prompt, ['happy', 'sad', 'angry', 'surprised', 'neutral', 'excited', 'worried'], ['front', 'left', 'right', 'back'], characterPath ); if (!imageGeneration.success) { throw new Error(`Image generation failed: ${imageGeneration.error}`); } // Step 5: Save character data to Notion database console.log('πŸ’Ύ Saving character to Notion database...'); const characterPage = await this.saveCharacterToDatabase( characterName, description, claudeAnalysis.traits, culturalBackground, imageGeneration.affogatoId ); console.log('βœ… Character creation completed successfully'); return { success: true, character: { name: characterName, description: description, traits: claudeAnalysis.traits, prompts: promptGeneration.prompts, images: imageGeneration.results, notionPage: characterPage, affogatoId: imageGeneration.affogatoId, outputPath: characterPath } }; } catch (error) { console.error(`❌ Character creation failed: ${error.message}`); return { success: false, error: error.message }; } } // Analyze existing character images with Claude async analyzeExistingCharacterImages(characterName, imagePathsOrBase64Array) { console.log(`πŸ“Έ ANALYZING EXISTING CHARACTER IMAGES`); console.log('=====================================\n'); try { let imageBase64Array = []; // Convert file paths to base64 if needed if (typeof imagePathsOrBase64Array[0] === 'string' && imagePathsOrBase64Array[0].includes('/')) { // Assume file paths for (const imagePath of imagePathsOrBase64Array) { if (fs.existsSync(imagePath)) { const imageBuffer = fs.readFileSync(imagePath); const base64 = imageBuffer.toString('base64'); imageBase64Array.push(base64); } } } else { // Assume already base64 imageBase64Array = imagePathsOrBase64Array; } if (imageBase64Array.length === 0) { throw new Error('No valid images found for analysis'); } // Analyze images with Claude console.log(`πŸ” Analyzing ${imageBase64Array.length} images with Claude AI...`); const imageAnalysis = await this.claudeAnalyzer.analyzeCharacterImages( imageBase64Array, characterName ); if (!imageAnalysis.success) { throw new Error(`Image analysis failed: ${imageAnalysis.error}`); } // Save analysis to character database const notion = await this.getNotionClient(); if (!this.dbEnhancer) { this.dbEnhancer = new DatabaseSchemaEnhancer(notion); } // Create or update character in database const characterPage = await this.saveCharacterToDatabase( characterName, `Character analyzed from ${imageBase64Array.length} reference images`, imageAnalysis.traits, null, // No cultural background specified null // No Affogato ID yet ); console.log('βœ… Character image analysis completed'); return { success: true, analysis: imageAnalysis, notionPage: characterPage, imageCount: imageBase64Array.length }; } catch (error) { console.error(`❌ Character image analysis failed: ${error.message}`); return { success: false, error: error.message }; } } }

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/bermingham85/mcp-puppet-pipeline'

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