255 lines
10 KiB
TypeScript
255 lines
10 KiB
TypeScript
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('=')[1])
|
|
: 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 = `Generate a welcome email for a new customer named ${customer.name} from ${customer.city}.
|
|
This is the first contact with them. The email should be professional but friendly.
|
|
Return ONLY a JSON object with 'subject' and 'body' fields. The body should be in HTML format.
|
|
|
|
instructions
|
|
1. This email is proposal for custom AI video generation.
|
|
2. Please includde your prposal for the simple video for the museum.
|
|
3. Please use the language of the city ${customer.city}`;
|
|
}/* 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.' },
|
|
{ role: 'user', content: prompt }
|
|
],
|
|
temperature: temperature,
|
|
max_tokens: 1000
|
|
},
|
|
{
|
|
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: `Update from Our Company - Contact #${emailCount + 1}`,
|
|
body: `<h1>Hello ${customer.name}!</h1><p>Thank you for your continued interest in our services. We appreciate your business.</p>`
|
|
};
|
|
}
|
|
} 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: `Important Update - Contact #${emailCount + 1}`,
|
|
body: `<h1>Hello ${customer.name}!</h1><p>Thank you for your interest in our services. We look forward to working with you!</p>`
|
|
};
|
|
}
|
|
}
|
|
|
|
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');
|
|
} else {
|
|
// Find customers with exactly N email contact records
|
|
customersQuery = customersQuery
|
|
.leftJoin(
|
|
ContactRecord,
|
|
'contact',
|
|
'contact.customerId = customer.id AND contact.contactType = :contactType',
|
|
{ contactType: 'EMAIL' }
|
|
)
|
|
.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)
|
|
emailContent.body // HTML version
|
|
);
|
|
|
|
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);
|
|
});
|