stock-management-demo/src-ssr/services/emailSummarizer.js

199 lines
6.9 KiB
JavaScript

import Imap from 'node-imap';
import { simpleParser } from 'mailparser';
import { GoogleGenAI } from '@google/genai';
import prisma from '../database.js';
// --- Environment Variables ---
const { GOOGLE_API_KEY } = process.env; // Added
// --- AI Setup ---
const ai = GOOGLE_API_KEY ? new GoogleGenAI({
apiKey: GOOGLE_API_KEY,
}) : null; // Added
export async function fetchAndFormatEmails() {
return new Promise((resolve, reject) => {
const imapConfig = {
user: process.env.OUTLOOK_EMAIL_ADDRESS,
password: process.env.OUTLOOK_APP_PASSWORD,
host: 'outlook.office365.com',
port: 993,
tls: true,
tlsOptions: { rejectUnauthorized: false } // Adjust as needed for your environment
};
const imap = new Imap(imapConfig);
const emailsJson = [];
function openInbox(cb) {
// Note: IMAP uses '/' as hierarchy separator, adjust if your server uses something else
imap.openBox('SLSNotifications/Reports/Backups', false, cb);
}
imap.once('ready', () => {
openInbox((err, box) => {
if (err) {
imap.end();
return reject(new Error(`Error opening mailbox: ${err.message}`));
}
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const searchCriteria = [['SINCE', yesterday.toISOString().split('T')[0]]]; // Search since midnight yesterday
const fetchOptions = { bodies: ['HEADER.FIELDS (SUBJECT DATE)', 'TEXT'], struct: true };
imap.search(searchCriteria, (searchErr, results) => {
if (searchErr) {
imap.end();
return reject(new Error(`Error searching emails: ${searchErr.message}`));
}
if (results.length === 0) {
console.log('No emails found from the last 24 hours.');
imap.end();
return resolve([]);
}
const f = imap.fetch(results, fetchOptions);
let processedCount = 0;
f.on('message', (msg, seqno) => {
let header = '';
let body = '';
msg.on('body', (stream, info) => {
let buffer = '';
stream.on('data', (chunk) => {
buffer += chunk.toString('utf8');
});
stream.once('end', () => {
if (info.which === 'TEXT') {
body = buffer;
} else {
// Assuming HEADER.FIELDS (SUBJECT DATE) comes as one chunk
header = buffer;
}
});
});
msg.once('attributes', (attrs) => {
// Attributes might contain date if not fetched via header
});
msg.once('end', async () => {
try {
// Use mailparser to handle potential encoding issues and structure
const mail = await simpleParser(`Subject: ${header.match(/Subject: (.*)/i)?.[1] || ''}\nDate: ${header.match(/Date: (.*)/i)?.[1] || ''}\n\n${body}`);
emailsJson.push({
title: mail.subject || 'No Subject',
time: mail.date ? mail.date.toISOString() : 'No Date',
body: mail.text || mail.html || 'No Body Content' // Prefer text, fallback to html, then empty
});
} catch (parseErr) {
console.error(`Error parsing email seqno ${seqno}:`, parseErr);
// Decide if you want to reject or just skip this email
}
processedCount++;
if (processedCount === results.length) {
// This check might be slightly inaccurate if errors occur,
// but it's a common pattern. Consider refining with promises.
}
});
});
f.once('error', (fetchErr) => {
console.error('Fetch error: ' + fetchErr);
// Don't reject here immediately, might still get some emails
});
f.once('end', () => {
console.log('Done fetching all messages!');
imap.end();
});
});
});
});
imap.once('error', (err) => {
reject(new Error(`IMAP Connection Error: ${err.message}`));
});
imap.once('end', () => {
console.log('IMAP Connection ended.');
resolve(emailsJson); // Resolve with the collected emails
});
imap.connect();
});
}
// --- Email Summary Logic (New Function) ---
export async function generateAndStoreEmailSummary() {
console.log('Attempting to generate and store Email summary...');
if (!ai) {
console.error('Google AI API key not configured. Skipping email summary generation.');
return;
}
try {
// Get the prompt from the database settings using Prisma
const setting = await prisma.setting.findUnique({
where: { key: 'emailPrompt' }, // Use 'emailPrompt' as the key
select: { value: true }
});
const promptTemplate = setting?.value;
if (!promptTemplate) {
console.error('Email prompt not found in database settings (key: emailPrompt). Skipping summary generation.');
return;
}
const emails = await fetchAndFormatEmails();
let summaryText;
if (emails.length === 0) {
summaryText = "No relevant emails found in the last 24 hours.";
console.log('No recent emails found for summary.');
} else {
console.log(`Found ${emails.length} recent emails. Generating summary...`);
// Replace placeholder in the prompt template
// Ensure your prompt template uses $EMAIL_DATA
let prompt = promptTemplate.replaceAll("$EMAIL_DATA", JSON.stringify(emails, null, 2));
// Call the AI model (adjust model name and config as needed)
const response = await ai.models.generateContent({
"model": "gemini-2.5-preview-04-17",
"contents": prompt,
config: {
temperature: 0 // Adjust temperature as needed
}
});
summaryText = response.text;
console.log('Email summary generated successfully by AI.');
}
// Store the summary in the database using Prisma upsert
const today = new Date();
today.setUTCHours(0, 0, 0, 0); // Use UTC start of day for consistency
await prisma.emailSummary.upsert({
where: { summaryDate: today },
update: {
summaryText: summaryText,
// generatedAt is updated automatically by @default(now())
},
create: {
summaryDate: today,
summaryText: summaryText,
},
});
console.log(`Email summary for ${today.toISOString().split('T')[0]} stored/updated in the database.`);
} catch (error) {
console.error("Error during Email summary generation/storage:", error);
// Re-throw or handle as appropriate for your application
throw error;
}
}