import express from 'express';
import multer from 'multer';
import path from 'path';
import fs from 'fs/promises';
import { ProductionOrchestrator } from './integrations/production-orchestrator.js';
import OpenAI from 'openai';
export class WebDashboardServer {
constructor() {
this.app = express();
this.orchestrator = new ProductionOrchestrator();
this.openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
this.setupMiddleware();
this.setupRoutes();
}
setupMiddleware() {
// Serve static files from public directory
this.app.use(express.static('public'));
// Serve attached_assets directory
this.app.use('/attached_assets', express.static('attached_assets'));
// Parse JSON bodies
this.app.use(express.json());
// Setup file upload
const storage = multer.diskStorage({
destination: async function (req, file, cb) {
const uploadDir = './uploads';
try {
await fs.mkdir(uploadDir, { recursive: true });
} catch (error) {
console.log('Upload directory already exists');
}
cb(null, uploadDir);
},
filename: function (req, file, cb) {
const timestamp = Date.now();
const ext = path.extname(file.originalname);
cb(null, `character_${timestamp}${ext}`);
}
});
this.upload = multer({
storage: storage,
limits: {
fileSize: 10 * 1024 * 1024 // 10MB limit
},
fileFilter: (req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only image files are allowed'));
}
}
});
}
setupRoutes() {
// Serve the dashboard
this.app.get('/', (req, res) => {
res.sendFile(path.resolve('public/index.html'));
});
// API endpoint for backstory enhancement
this.app.post('/api/enhance-backstory', async (req, res) => {
try {
const { characterName, basicBackstory, personalityTraits } = req.body;
console.log('๐ญ Enhancing backstory with OpenAI...');
const traitsText = personalityTraits.length > 0
? `Personality traits: ${personalityTraits.join(', ')}`
: '';
const prompt = `You are a creative character development expert for puppet shows. Take this basic backstory and enhance it into a rich, detailed character background suitable for a puppet character.
Character Name: ${characterName}
Basic Backstory: ${basicBackstory}
${traitsText}
Create an enhanced backstory that includes:
1. Detailed background and history
2. Motivations and goals
3. Relationships and connections
4. Unique quirks and mannerisms
5. Speaking style and catchphrases
6. Fun facts and interesting details
Make it engaging, family-friendly, and perfect for puppet show productions. The enhanced backstory should be 2-3 paragraphs and bring the character to life.`;
const response = await this.openai.chat.completions.create({
model: "gpt-4",
messages: [
{ role: "system", content: "You are a creative character development expert specializing in puppet show characters." },
{ role: "user", content: prompt }
],
temperature: 0.8,
max_tokens: 800
});
const enhancedBackstory = response.choices[0].message.content;
res.json({
success: true,
enhancedBackstory: enhancedBackstory
});
} catch (error) {
console.error('โ Backstory enhancement failed:', error.message);
res.status(500).json({
success: false,
error: error.message
});
}
});
// API endpoint for character creation with streaming progress
this.app.post('/api/create-character', this.upload.single('image'), async (req, res) => {
try {
// Set up Server-Sent Events
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Cache-Control'
});
const sendProgress = (type, data) => {
res.write(`data: ${JSON.stringify({ type, ...data })}\n\n`);
};
// Extract form data
const {
characterName,
characterType,
personalityTraits,
basicBackstory,
enhancedBackstory,
voiceDescription,
voiceId,
countryOrigin,
accent,
generateImages,
storeDatabase,
prepareVoice
} = req.body;
const imagePath = req.file.path;
const traits = JSON.parse(personalityTraits || '[]');
sendProgress('log', { message: `๐ญ Starting production for ${characterName}...` });
sendProgress('progress', { progress: 10, message: 'Analyzing character image...' });
// Build comprehensive character data
const characterData = {
name: characterName,
description: enhancedBackstory || basicBackstory,
characterType: characterType,
referenceImagePath: imagePath,
personalityTraits: traits,
voiceDescription: voiceDescription || '',
voiceId: voiceId || '',
countryOrigin: countryOrigin || '',
accent: accent || '',
characterTraits: {
physical: {
size: 'medium',
material: 'felt and foam construction',
skin_tone: 'consistent with reference image',
hair: 'styled to match reference',
eyes: 'white circular eyes with black pupils',
mouth: 'mechanical opening/closing mouth'
},
clothing: {
style: 'based on reference image clothing',
colors: ['varied based on reference'],
construction: 'removable puppet clothing',
accessories: []
},
personality: {
traits: traits,
expressions: 'varied emotional expressions',
puppet_behavior: 'bouncy puppet-like movement'
},
puppet_mechanics: {
mouth_operation: 'mechanical open/close',
hand_controls: 'control rods visible',
eye_movement: 'controlled positioning',
posture: 'upright puppet pose'
},
cultural_background: 'Universal',
voice_profile: voiceDescription || 'friendly puppet voice'
}
};
sendProgress('progress', { progress: 30, message: 'Processing character through production pipeline...' });
// Process through your MCP production pipeline
const productionResult = await this.orchestrator.processCharacterImage(imagePath, characterName);
if (productionResult.success) {
sendProgress('log', { message: 'โ
Character analysis completed' });
sendProgress('progress', { progress: 60, message: 'Generating puppet images...' });
// If generateImages is enabled, proceed with full pipeline
if (generateImages === 'true') {
sendProgress('log', { message: '๐จ Creating character in Affogato...' });
try {
const pipelineResult = await this.orchestrator.characterPipeline.createCharacterFromDescription(characterData);
if (pipelineResult.success) {
sendProgress('log', { message: 'โ
Character images generated successfully' });
sendProgress('progress', { progress: 90, message: 'Finalizing character data...' });
// Store additional data if requested
if (storeDatabase === 'true') {
sendProgress('log', { message: '๐ Storing in databases...' });
}
sendProgress('progress', { progress: 100, message: 'Production complete!' });
sendProgress('complete', {
results: {
success: true,
character_name: characterName,
character: pipelineResult.character,
puppet_traits: productionResult.puppet_traits,
voice_profile: productionResult.voice_profile,
ready_for_voice_generation: productionResult.ready_for_voice_generation,
ready_for_scripting: productionResult.ready_for_scripting
}
});
} else {
throw new Error(pipelineResult.error);
}
} catch (pipelineError) {
sendProgress('log', { message: `โ ๏ธ Image generation failed: ${pipelineError.message}` });
sendProgress('log', { message: '๐ Returning character analysis results only...' });
sendProgress('progress', { progress: 100, message: 'Character analysis complete (images failed)' });
sendProgress('complete', {
results: {
success: true,
character_name: characterName,
puppet_traits: productionResult.puppet_traits,
voice_profile: productionResult.voice_profile,
ready_for_voice_generation: productionResult.ready_for_voice_generation,
ready_for_scripting: productionResult.ready_for_scripting,
note: 'Character analyzed successfully, but image generation failed. Character traits and voice profile are ready.'
}
});
}
} else {
// Just return analysis results
sendProgress('progress', { progress: 100, message: 'Character analysis complete!' });
sendProgress('complete', {
results: {
success: true,
character_name: characterName,
puppet_traits: productionResult.puppet_traits,
voice_profile: productionResult.voice_profile,
ready_for_voice_generation: productionResult.ready_for_voice_generation,
ready_for_scripting: productionResult.ready_for_scripting
}
});
}
} else {
throw new Error(productionResult.error);
}
res.end();
} catch (error) {
console.error('โ Character creation failed:', error.message);
res.write(`data: ${JSON.stringify({
type: 'error',
message: error.message
})}\n\n`);
res.end();
}
});
// Get ElevenLabs voices for dropdown
this.app.get('/api/voices', async (req, res) => {
try {
const { ElevenLabsClient } = await import('./integrations/elevenlabs-client.js');
const elevenLabs = new ElevenLabsClient();
const voices = await elevenLabs.getVoices();
res.json({
success: true,
voices: voices
});
} catch (error) {
console.error('โ Voice fetching failed:', error.message);
res.status(500).json({
success: false,
error: error.message,
voices: [] // Return empty array for form to handle gracefully
});
}
});
// Generate voice sample for testing
this.app.post('/api/voice-sample', async (req, res) => {
try {
const { voiceId, text } = req.body;
if (!voiceId) {
return res.status(400).json({
success: false,
error: 'Voice ID is required'
});
}
const { ElevenLabsClient } = await import('./integrations/elevenlabs-client.js');
const elevenLabs = new ElevenLabsClient();
const sampleText = text || "Hello! This is how I sound as your character's voice.";
const sample = await elevenLabs.generateVoiceSample(voiceId, sampleText);
res.json({
success: true,
audio_base64: sample.audio_base64,
content_type: sample.content_type
});
} catch (error) {
console.error('โ Voice sample generation failed:', error.message);
res.status(500).json({
success: false,
error: error.message
});
}
});
// Check character name uniqueness
this.app.post('/api/validate-character', async (req, res) => {
try {
const { characterName } = req.body;
if (!characterName) {
return res.status(400).json({
success: false,
error: 'Character name is required'
});
}
// Check PostgreSQL database for existing character
const { Pool } = await import('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
const query = 'SELECT COUNT(*) FROM characters WHERE LOWER(name) = LOWER($1)';
const result = await pool.query(query, [characterName]);
const exists = parseInt(result.rows[0].count) > 0;
await pool.end();
res.json({
success: true,
exists: exists,
message: exists ? 'Character name already exists' : 'Character name is available'
});
} catch (error) {
console.error('โ Character validation failed:', error.message);
res.json({
success: true,
exists: false, // Default to false if validation fails
message: 'Validation unavailable, but name will be checked during creation'
});
}
});
// Health check endpoint
this.app.get('/api/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
services: {
production_pipeline: 'operational',
openai: process.env.OPENAI_API_KEY ? 'connected' : 'not_configured',
affogato: process.env.AFFOGATO_API_KEY ? 'connected' : 'not_configured',
notion: 'connected',
elevenlabs: process.env.ELEVENLABS_API_KEY ? 'connected' : 'key_missing'
}
});
});
}
start(port = 5000) {
this.app.listen(port, '0.0.0.0', () => {
console.log('');
console.log('๐ญ Puppet Production Dashboard Server Started!');
console.log('==========================================');
console.log(`๐ฑ Dashboard: http://localhost:${port}`);
console.log(`๐ง API Health: http://localhost:${port}/api/health`);
console.log(`โ๏ธ Production Pipeline: Ready`);
console.log(`๐ค OpenAI Integration: ${process.env.OPENAI_API_KEY ? 'Connected' : 'Not configured'}`);
console.log(`๐จ Affogato Integration: ${process.env.AFFOGATO_API_KEY ? 'Connected' : 'Not configured'}`);
console.log(`๐ Notion Integration: Connected via Replit`);
console.log('');
});
}
}