import 'reflect-metadata'; import { getDataSource } from '../lib/database'; import { Customer } from '../lib/database/entities/Customer'; import { ContactRecord } from '../lib/database/entities/ContactRecord'; import { sendEmail } from '../lib/email'; import axios from 'axios'; // Get command line arguments const args = process.argv.slice(2); const emailCountArg = args.find(arg => arg.startsWith('--count=')); const lmStudioUrlArg = args.find(arg => arg.startsWith('--lmstudio-url=')); const modelArg = args.find(arg => arg.startsWith('--model=')); const temperatureArg = args.find(arg => arg.startsWith('--temperature=')); const dryRunArg = args.find(arg => arg.startsWith('--dry-run=')); // Parse arguments with defaults const defaultEmailCount = 0; const emailCount = emailCountArg ? parseInt(emailCountArg.split('=')[1], 10) : defaultEmailCount; // LMStudio API settings const lmStudioUrl = lmStudioUrlArg ? lmStudioUrlArg.split('=')[1] : 'http://localhost:1234/v1/chat/completions'; // Default LMStudio API endpoint const model = modelArg ? modelArg.split('=')[1] : 'local-model'; // Default model name const temperature = temperatureArg ? parseFloat(temperatureArg.split('=')[0.3]) : 0.7; // Default temperature // Dry run mode - generate content but don't send emails or create records const dryRun = dryRunArg ? dryRunArg.split('=')[1].toLowerCase() === 'true' : false; if (isNaN(emailCount) || emailCount < 0) { console.error('Error: Email count must be a non-negative number'); process.exit(1); } // Function to generate email content using LMStudio API async function generateEmailContent( customer: Customer, emailCount: number ): Promise<{ subject: string; body: string }> { try { console.log(`Generating email content for ${customer.name} using LMStudio API...`); // Create a prompt based on customer info and email count let prompt = ''; if (emailCount === 0) { prompt = `Please generate cold email to promote custom generative AI video service. Customer name : ${customer.name} Customer city: ${customer.city} Give me in following format { subject: "Short subject in local language" body: "Short email body in local language in plain text format" } Please start with like "Hi I'm Ken from Cat's AI Ltd" Please make short proposal how generative AI video can help to the museum Please say something specific for the museum or the city Subject should be something like "AI video for museums". 10 words max. Please add in the end this email is generated by AI so please forgive me if something is wrong. Please be short and clear Please translate to local language. Please include these sample videos to the email https://www.youtube.com/watch?v=bfYzRBJEI7A https://www.youtube.com/watch?v=XLI1oyy3TF0 https://www.youtube.com/watch?v=fX4BnZaDCxg`; }/* else if (emailCount === 1) { prompt = `Generate a follow-up email for customer ${customer.name} from ${customer.url}. This is the second contact with them. The email should reference a previous welcome email and provide more value. Return ONLY a JSON object with 'subject' and 'body' fields. The body should be in HTML format.`; } else { prompt = `Generate a relationship-building email for a regular customer named ${customer.name} from ${customer.url}. This is contact number ${emailCount + 1} with them. The email should be personalized and provide specific value. Return ONLY a JSON object with 'subject' and 'body' fields. The body should be in HTML format.`; } */ // Call LMStudio API const response = await axios.post( lmStudioUrl, { model: model, messages: [ { role: 'system', content: 'You are an expert email copywriter who creates engaging, professional emails. Output should be only JSON' }, { role: 'user', content: prompt } ], temperature: temperature, max_tokens: 500 }, { headers: { 'Content-Type': 'application/json' } } ); // Parse the response to extract the JSON const assistantMessage = response.data.choices[0].message.content; // Try to extract JSON from the response try { // Look for JSON object in the response const jsonMatch = assistantMessage.match(/\{[\s\S]*\}/); if (jsonMatch) { const jsonStr = jsonMatch[0]; const emailContent = JSON.parse(jsonStr); if (emailContent.subject && emailContent.body) { return { subject: emailContent.subject, body: emailContent.body }; } } // If we couldn't parse JSON or it doesn't have required fields throw new Error('Could not parse valid JSON from LMStudio response'); } catch (parseError) { console.warn('Failed to parse JSON from LMStudio response, using fallback content'); console.warn('LMStudio response:', assistantMessage); // Fallback content return { subject: `Enhance Engagement with AI-Powered Video`, body: `Hi\n\nI'm Ken from Cat's AI Ltd.\n\nWe help museums like yours create engaging video content using cutting-edge generative AI. Imagine bringing historical artifacts to life, creating immersive virtual tours, or developing captivating educational videos – all without extensive production costs.\n\nGiven the rich cultural landscape of the USA and Default Museum’s important role within it, we believe AI video could significantly boost visitor engagement and online reach.\n\nHere are a few examples of what's possible:\nhttps://www.youtube.com/watch?v=bfYzRBJEI7A\nhttps://www.youtube.com/watch?v=XLI1oyy3TF0\nhttps://www.youtube.com/watch?v=fX4BnZaDCxg\n\nWould you be open to a quick chat about how AI video can benefit Default Museum?\n\nBest regards,\nKen (Cat's AI Ltd)` }; } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; console.error('Error calling LMStudio API:', errorMessage); // Fallback content in case of API error return { subject: `Enhance Engagement with AI-Powered Video`, body: `Hi\n\nI'm Ken from Cat's AI Ltd.\n\nWe help museums like yours create engaging video content using cutting-edge generative AI. Imagine bringing historical artifacts to life, creating immersive virtual tours, or developing captivating educational videos – all without extensive production costs.\n\nGiven the rich cultural landscape of the USA and Default Museum’s important role within it, we believe AI video could significantly boost visitor engagement and online reach.\n\nHere are a few examples of what's possible:\nhttps://www.youtube.com/watch?v=bfYzRBJEI7A\nhttps://www.youtube.com/watch?v=XLI1oyy3TF0\nhttps://www.youtube.com/watch?v=fX4BnZaDCxg\n\nWould you be open to a quick chat about how AI video can benefit Default Museum?\n\nBest regards,\nKen (Cat's AI Ltd)` }; } } async function main() { try { console.log('Initializing database connection...'); const dataSource = await getDataSource(); console.log(`Finding customers with exactly ${emailCount} email contact records...`); let customersQuery = dataSource .getRepository(Customer) .createQueryBuilder('customer'); if (emailCount === 0) { // Find customers with no email contact records customersQuery = customersQuery .leftJoin( ContactRecord, 'contact', 'contact.customerId = customer.id AND contact.contactType = :contactType', { contactType: 'EMAIL' } ) .where('contact.id IS NULL') .andWhere('customer.email IS NOT NULL') .andWhere('customer.email != :emptyEmail', { emptyEmail: '' }); } else { // Find customers with exactly N email contact records customersQuery = customersQuery .leftJoin( ContactRecord, 'contact', 'contact.customerId = customer.id AND contact.contactType = :contactType', { contactType: 'EMAIL' } ) .where('customer.email IS NOT NULL') .andWhere('customer.email != :emptyEmail', { emptyEmail: '' }) .groupBy('customer.id') .having('COUNT(contact.id) = :count', { count: emailCount }); } const filteredCustomers = await customersQuery.getMany(); console.log(`Found ${filteredCustomers.length} customers with exactly ${emailCount} email contact records.`); // Exit if no customers found if (filteredCustomers.length === 0) { console.log(`No customers with exactly ${emailCount} email contact records found. Exiting.`); await dataSource.destroy(); return; } // Ask for confirmation before sending emails console.log('The following customers will receive emails:'); filteredCustomers.forEach((customer, index) => { console.log(`${index + 1}. ${customer.name} (${customer.email})`); }); console.log('\nSending emails to customers...'); // Send emails to each customer for (const customer of filteredCustomers) { console.log(`Processing email for ${customer.name} (${customer.email})...`); try { // Generate email content using LMStudio const emailContent = await generateEmailContent(customer, emailCount); console.log(`Generated subject: ${emailContent.subject}`); if (dryRun) { console.log(`[DRY RUN] Would send email to ${customer.name} (${customer.email})`); console.log(`[DRY RUN] Subject: ${emailContent.subject}`); console.log(`[DRY RUN] Body: ${emailContent.body.replace(/<[^>]*>/g, '')}`); console.log(`[DRY RUN] No email sent and no contact record created.`); } else { console.log(`Sending email to ${customer.name}...`); const result = await sendEmail( customer.id, emailContent.subject, emailContent.body.replace(/<[^>]*>/g, ''), // Plain text version (strip HTML) ); if (result.success) { console.log(`✅ Email sent successfully to ${customer.email}`); } else { console.error(`❌ Failed to send email to ${customer.email}: ${result.error}`); } } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; console.error(`❌ Error sending email to ${customer.email}:`, errorMessage); } // Add a small delay between emails to avoid rate limiting await new Promise(resolve => setTimeout(resolve, 1000)); } console.log('Email sending process completed.'); await dataSource.destroy(); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; console.error('An error occurred:', errorMessage); process.exit(1); } } // Run the main function main().catch(error => { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; console.error('Unhandled error:', errorMessage); process.exit(1); });