Skip to main content
Glama
web-server.jsโ€ข18.4 kB
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(''); }); } }

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