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
};
}
}
}