Add mantis summaries and start of email summaries... Need to figure out a way to get the emails since MS block IMAP :(
This commit is contained in:
parent
2d11d0bd79
commit
2ad9a63582
18 changed files with 1993 additions and 577 deletions
199
src-ssr/services/emailSummarizer.js
Normal file
199
src-ssr/services/emailSummarizer.js
Normal file
|
@ -0,0 +1,199 @@
|
|||
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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue