import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; import { firstValueFrom } from "rxjs"; import { AiService } from "./ai.service"; import { AiMessage } from "../../../common/enums/message.enum"; import { MessageDetails } from "../../mail-server/interfaces/messages-response.interface"; import { WildDuckMessagesService } from "../../mail-server/services/wildduck-messages.service"; import { GenerateTemplateDto, TemplatePurposeEnum, TemplateStyleEnum, TemplateThemeEnum } from "../DTO/generate-template.dto"; import { SummarizeEmailDto } from "../DTO/summarize-email.dto"; import { WriteEmailAssistanceDto } from "../DTO/write-email-assistance.dto"; import { SummaryLengthEnum, SummaryToneEnum } from "../enums/ai.enum"; import { IEmailData, ITemplateGenerationRequest, SummarizationOptions, TemplateGenerationOptions } from "../interfaces/ai-service.interface"; @Injectable() export class AiAssistantService { constructor( private readonly messagesService: WildDuckMessagesService, private readonly aiService: AiService, ) {} //************************************************* */ async summarizeEmail(summarizeDto: SummarizeEmailDto, userId: string) { const message = await firstValueFrom(this.messagesService.getMessage(userId, summarizeDto.mailbox, summarizeDto.messageId)); if (!message) throw new NotFoundException(AiMessage.MESSAGE_NOT_FOUND_OR_ACCESS_DENIED); const emailData = this.extractEmailData(message); const summary = await this.generateEmailSummary(emailData, summarizeDto.length, summarizeDto.tone); return summary; } //************************************************* */ async assistEmailWriting(writeAssistanceDto: WriteEmailAssistanceDto, userId: string) { const assistance = await this.generateEmailWritingAssistance(writeAssistanceDto, userId); return { assistance }; } //************************************************* */ async generateTemplate(generateTemplateDto: GenerateTemplateDto) { if (!generateTemplateDto.description?.trim()) { throw new BadRequestException(AiMessage.DESCRIPTION_IS_REQUIRED); } const templateRequest: ITemplateGenerationRequest = { description: generateTemplateDto.description.trim(), options: this.buildTemplateGenerationOptions(generateTemplateDto), }; const aiResponse = await this.aiService.generateTemplate(templateRequest); return { template: { name: aiResponse.templateName, description: aiResponse.description, structure: aiResponse.structure, rawHtml: aiResponse.rawHtml, colorScheme: aiResponse.colorScheme, }, designInsights: { designPrinciples: aiResponse.designPrinciples, suggestedImprovements: aiResponse.suggestedImprovements, }, metadata: { theme: generateTemplateDto.theme || TemplateThemeEnum.MODERN, style: generateTemplateDto.style || TemplateStyleEnum.PROFESSIONAL, purpose: generateTemplateDto.purpose || TemplatePurposeEnum.GENERAL, generatedAt: new Date().toISOString(), }, }; } //************************************************* */ private buildTemplateGenerationOptions(dto: GenerateTemplateDto): TemplateGenerationOptions { return { theme: dto.theme, style: dto.style, purpose: dto.purpose, templateName: dto.templateName, preferredColors: dto.preferredColors, }; } //************************************************* */ private extractEmailData(message: MessageDetails) { return { subject: message.subject, content: this.extractTextContent(message), htmlContent: this.extractHtmlContent(message), from: this.extractSenderInfo(message), to: this.extractRecipientInfo(message), date: message.date || new Date().toISOString(), attachments: message.attachments || [], }; } //************************************************* */ private extractTextContent(message: MessageDetails): string { if (message.text) { return typeof message.text === "string" ? message.text : message.text || ""; } if (message.html) { return this.stripHtmlTags(typeof message.html === "string" ? message.html : message.html[0] || ""); } return ""; } //************************************************* */ private extractHtmlContent(message: MessageDetails): string { if (message.html) { return typeof message.html === "string" ? message.html : message.html[0] || ""; } return ""; } //************************************************* */ private extractSenderInfo(message: MessageDetails): string { if (message.from && message.from.address) { return message.from.name ? `${message.from.name} <${message.from.address}>` : message.from.address; } return ""; } //************************************************* */ private extractRecipientInfo(message: MessageDetails): string[] { const recipients: string[] = []; if (message.to && Array.isArray(message.to)) { recipients.push(...message.to.map((recipient) => (recipient.name ? `${recipient.name} <${recipient.address}>` : recipient.address))); } return recipients; } //************************************************* */ private stripHtmlTags(html: string): string { return html .replace(/<[^>]*>/g, "") .replace(/ /g, " ") .trim(); } //************************************************* */ private async generateEmailSummary(emailData: IEmailData, length?: SummaryLengthEnum, tone?: SummaryToneEnum) { const content = emailData.content || emailData.htmlContent; if (!content || !content.trim()) throw new BadRequestException(AiMessage.EMAIL_CONTENT_IS_EMPTY_OR_COULD_NOT_BE_EXTRACTED); const aiEmailData: IEmailData = { subject: emailData.subject, content: emailData.content, htmlContent: emailData.htmlContent, from: emailData.from, to: emailData.to, date: emailData.date, attachments: emailData.attachments, }; const summarizationOptions: SummarizationOptions = { length: length || SummaryLengthEnum.MEDIUM, tone: tone || SummaryToneEnum.PROFESSIONAL, }; const aiResponse = await this.aiService.summarizeEmail(aiEmailData, summarizationOptions); return { summary: aiResponse.summary, keyPoints: aiResponse.keyPoints, suggestedActions: aiResponse.suggestedActions, priority: aiResponse.priority, sentiment: aiResponse.sentiment, }; } //************************************************* */ private async generateEmailWritingAssistance(writeAssistanceDto: WriteEmailAssistanceDto, _userId: string) { const aiResponse = await this.aiService.assistEmailWriting(writeAssistanceDto.prompt); return { suggestion: { subject: aiResponse.subject, content: aiResponse.content, alternatives: aiResponse.alternatives, }, tips: aiResponse.tips, improvements: aiResponse.improvements, }; } }