update: the ai interface and enum
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsEnum, IsNotEmpty, IsNumber, IsOptional, IsString } from "class-validator";
|
||||
|
||||
import { SummaryLength, SummaryTone } from "../enums/ai.enum";
|
||||
import { SummaryLengthEnum, SummaryToneEnum } from "../enums/ai.enum";
|
||||
|
||||
export class SummarizeEmailDto {
|
||||
@IsNotEmpty({ message: "Message ID is required" })
|
||||
@@ -15,12 +15,12 @@ export class SummarizeEmailDto {
|
||||
mailbox: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(SummaryLength, { message: "Summary length must be one of: short, medium, detailed" })
|
||||
@ApiPropertyOptional({ description: "Preferred summary length", enum: SummaryLength, default: SummaryLength.MEDIUM })
|
||||
length?: SummaryLength;
|
||||
@IsEnum(SummaryLengthEnum, { message: "Summary length must be one of: short, medium, detailed" })
|
||||
@ApiPropertyOptional({ description: "Preferred summary length", enum: SummaryLengthEnum, default: SummaryLengthEnum.MEDIUM })
|
||||
length?: SummaryLengthEnum;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(SummaryTone, { message: "Summary tone must be one of: formal, casual, professional" })
|
||||
@ApiPropertyOptional({ description: "Preferred summary tone", enum: SummaryTone, default: SummaryTone.PROFESSIONAL })
|
||||
tone?: SummaryTone;
|
||||
@IsEnum(SummaryToneEnum, { message: "Summary tone must be one of: formal, casual, professional" })
|
||||
@ApiPropertyOptional({ description: "Preferred summary tone", enum: SummaryToneEnum, default: SummaryToneEnum.PROFESSIONAL })
|
||||
tone?: SummaryToneEnum;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,35 @@
|
||||
export enum SummaryLength {
|
||||
export enum SummaryLengthEnum {
|
||||
SHORT = "short",
|
||||
MEDIUM = "medium",
|
||||
DETAILED = "detailed",
|
||||
}
|
||||
|
||||
export enum SummaryTone {
|
||||
export enum SentimentEnum {
|
||||
POSITIVE = "positive",
|
||||
NEUTRAL = "neutral",
|
||||
NEGATIVE = "negative",
|
||||
}
|
||||
|
||||
export enum LanguageEnum {
|
||||
ENGLISH = "English",
|
||||
PERSIAN = "Persian",
|
||||
}
|
||||
|
||||
export enum SummaryToneEnum {
|
||||
FORMAL = "formal",
|
||||
CASUAL = "casual",
|
||||
PROFESSIONAL = "professional",
|
||||
}
|
||||
|
||||
export enum EmailPurpose {
|
||||
export enum EmailPurposeEnum {
|
||||
GENERAL = "general",
|
||||
REPLY = "reply",
|
||||
FORWARD = "forward",
|
||||
NEW = "new",
|
||||
FOLLOW_UP = "follow_up",
|
||||
}
|
||||
|
||||
export enum WritingTone {
|
||||
export enum WritingToneEnum {
|
||||
FORMAL = "formal",
|
||||
CASUAL = "casual",
|
||||
PROFESSIONAL = "professional",
|
||||
@@ -25,7 +37,7 @@ export enum WritingTone {
|
||||
URGENT = "urgent",
|
||||
}
|
||||
|
||||
export enum EmailLength {
|
||||
export enum EmailLengthEnum {
|
||||
SHORT = "short",
|
||||
MEDIUM = "medium",
|
||||
LONG = "long",
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
export interface EmailData {
|
||||
import { PriorityEnum } from "../../email/enums/email-header.enum";
|
||||
import { MessageAttachment } from "../../mail-server/interfaces/messages-response.interface";
|
||||
import { LanguageEnum, SentimentEnum, SummaryLengthEnum, SummaryToneEnum } from "../enums/ai.enum";
|
||||
|
||||
export interface IEmailData {
|
||||
subject: string;
|
||||
content: string;
|
||||
htmlContent?: string;
|
||||
from: string;
|
||||
to: string[];
|
||||
date: Date;
|
||||
attachments?: unknown[];
|
||||
date: string;
|
||||
attachments?: boolean | MessageAttachment[];
|
||||
}
|
||||
|
||||
export interface SummarizationOptions {
|
||||
length: "short" | "medium" | "detailed";
|
||||
tone: "formal" | "casual" | "professional";
|
||||
language?: "Persian" | "English";
|
||||
length: SummaryLengthEnum;
|
||||
tone: SummaryToneEnum;
|
||||
language?: LanguageEnum;
|
||||
}
|
||||
|
||||
export interface AISummaryResponse {
|
||||
summary: string;
|
||||
keyPoints: string[];
|
||||
suggestedActions: string[];
|
||||
priority: "low" | "medium" | "high";
|
||||
sentiment: "positive" | "neutral" | "negative";
|
||||
priority: PriorityEnum;
|
||||
sentiment: SentimentEnum;
|
||||
}
|
||||
|
||||
export interface AIWritingResponse {
|
||||
@@ -31,6 +35,6 @@ export interface AIWritingResponse {
|
||||
}
|
||||
|
||||
export interface AIServiceInterface {
|
||||
summarizeEmail(emailData: EmailData, options: SummarizationOptions): Promise<AISummaryResponse>;
|
||||
summarizeEmail(emailData: IEmailData, options: SummarizationOptions): Promise<AISummaryResponse>;
|
||||
assistEmailWriting(prompt: string): Promise<AIWritingResponse>;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import { MessageDetails } from "../../mail-server/interfaces/messages-response.i
|
||||
import { WildDuckMessagesService } from "../../mail-server/services/wildduck-messages.service";
|
||||
import { SummarizeEmailDto } from "../DTO/summarize-email.dto";
|
||||
import { WriteEmailAssistanceDto } from "../DTO/write-email-assistance.dto";
|
||||
import { SummaryLength, SummaryTone } from "../enums/ai.enum";
|
||||
import { EmailData, SummarizationOptions } from "../interfaces/ai-service.interface";
|
||||
import { SummaryLengthEnum, SummaryToneEnum } from "../enums/ai.enum";
|
||||
import { IEmailData, SummarizationOptions } from "../interfaces/ai-service.interface";
|
||||
|
||||
@Injectable()
|
||||
export class AiAssistantService {
|
||||
@@ -45,7 +45,7 @@ export class AiAssistantService {
|
||||
htmlContent: this.extractHtmlContent(message),
|
||||
from: this.extractSenderInfo(message),
|
||||
to: this.extractRecipientInfo(message),
|
||||
date: message.date || new Date(),
|
||||
date: message.date || new Date().toISOString(),
|
||||
attachments: message.attachments || [],
|
||||
};
|
||||
}
|
||||
@@ -99,12 +99,12 @@ export class AiAssistantService {
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private async generateEmailSummary(emailData: any, length: SummaryLength = SummaryLength.MEDIUM, _tone: SummaryTone = SummaryTone.PROFESSIONAL) {
|
||||
private async generateEmailSummary(emailData: IEmailData, length?: SummaryLengthEnum, tone?: SummaryToneEnum) {
|
||||
const content = emailData.content || emailData.htmlContent;
|
||||
|
||||
if (!content.trim()) throw new BadRequestException(AiMessage.EMAIL_CONTENT_IS_EMPTY_OR_COULD_NOT_BE_EXTRACTED);
|
||||
if (!content || !content.trim()) throw new BadRequestException(AiMessage.EMAIL_CONTENT_IS_EMPTY_OR_COULD_NOT_BE_EXTRACTED);
|
||||
|
||||
const aiEmailData: EmailData = {
|
||||
const aiEmailData: IEmailData = {
|
||||
subject: emailData.subject,
|
||||
content: emailData.content,
|
||||
htmlContent: emailData.htmlContent,
|
||||
@@ -115,8 +115,8 @@ export class AiAssistantService {
|
||||
};
|
||||
|
||||
const summarizationOptions: SummarizationOptions = {
|
||||
length: length.toLowerCase() as "short" | "medium" | "detailed",
|
||||
tone: _tone.toLowerCase() as "formal" | "casual" | "professional",
|
||||
length: length || SummaryLengthEnum.MEDIUM,
|
||||
tone: tone || SummaryToneEnum.PROFESSIONAL,
|
||||
};
|
||||
|
||||
const aiResponse = await this.aiService.summarizeEmail(aiEmailData, summarizationOptions);
|
||||
|
||||
@@ -4,15 +4,16 @@ import OpenAI from "openai";
|
||||
import { AI_CONFIG } from "../../../common/constants";
|
||||
import { AiMessage } from "../../../common/enums/message.enum";
|
||||
import { AIConfig } from "../../../configs/ai.config";
|
||||
import { AIServiceInterface, AISummaryResponse, AIWritingResponse, EmailData, SummarizationOptions } from "../interfaces/ai-service.interface";
|
||||
import { EmailPurposeEnum, LanguageEnum, SummaryLengthEnum, SummaryToneEnum } from "../enums/ai.enum";
|
||||
import { AIServiceInterface, AISummaryResponse, AIWritingResponse, IEmailData, SummarizationOptions } from "../interfaces/ai-service.interface";
|
||||
|
||||
@Injectable()
|
||||
export class AiService implements AIServiceInterface {
|
||||
private readonly openai: OpenAI;
|
||||
private readonly language: "Persian" | "English" = "Persian";
|
||||
private readonly defaultTone: "formal" | "casual" | "professional" = "professional";
|
||||
private readonly defaultLength: "short" | "medium" | "detailed" = "medium";
|
||||
private readonly defaultPurpose: "general" | "reply" | "forward" | "new" | "follow_up" = "general";
|
||||
private readonly language: LanguageEnum = LanguageEnum.PERSIAN;
|
||||
private readonly defaultTone: SummaryToneEnum = SummaryToneEnum.PROFESSIONAL;
|
||||
private readonly defaultLength: SummaryLengthEnum = SummaryLengthEnum.MEDIUM;
|
||||
private readonly defaultPurpose: EmailPurposeEnum = EmailPurposeEnum.GENERAL;
|
||||
|
||||
constructor(@Inject(AI_CONFIG) private readonly aiConfig: AIConfig) {
|
||||
this.openai = new OpenAI({
|
||||
@@ -23,7 +24,7 @@ export class AiService implements AIServiceInterface {
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
async summarizeEmail(emailData: EmailData, options: SummarizationOptions): Promise<AISummaryResponse> {
|
||||
async summarizeEmail(emailData: IEmailData, options: SummarizationOptions): Promise<AISummaryResponse> {
|
||||
const prompt = this.buildSummarizationPrompt(emailData, options);
|
||||
|
||||
const completion = await this.openai.chat.completions.create({
|
||||
@@ -98,7 +99,7 @@ export class AiService implements AIServiceInterface {
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private buildSummarizationPrompt(emailData: EmailData, options: SummarizationOptions): string {
|
||||
private buildSummarizationPrompt(emailData: IEmailData, options: SummarizationOptions): string {
|
||||
const template = this.aiConfig.farsiPrompts.summarization.promptTemplate;
|
||||
let prompt = `${template.intro}\n\n`;
|
||||
|
||||
@@ -108,7 +109,7 @@ export class AiService implements AIServiceInterface {
|
||||
prompt += `${template.dateLabel} ${emailData.date}\n\n`;
|
||||
prompt += `${template.contentLabel}\n${emailData.content}\n\n`;
|
||||
|
||||
if (emailData.attachments && emailData.attachments.length > 0) {
|
||||
if (emailData.attachments && Array.isArray(emailData.attachments) && emailData.attachments.length > 0) {
|
||||
prompt += `${template.attachmentsLabel} ${emailData.attachments.length} فایل\n\n`;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ export const enum EmailHeaderKey {
|
||||
XTrustedSources = "X-Trusted-Sources",
|
||||
}
|
||||
|
||||
export enum Priority {
|
||||
export enum PriorityEnum {
|
||||
High = "high",
|
||||
Normal = "normal",
|
||||
Low = "low",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Priority } from "../enums/email-header.enum";
|
||||
import { PriorityEnum } from "../enums/email-header.enum";
|
||||
|
||||
export interface IGenerateAntiThreadingHeadersOptions {
|
||||
messageId?: string;
|
||||
@@ -8,7 +8,7 @@ export interface IGenerateAntiThreadingHeadersOptions {
|
||||
businessDomain: string;
|
||||
isTransactional?: boolean;
|
||||
isMarketing?: boolean;
|
||||
priority?: Priority;
|
||||
priority?: PriorityEnum;
|
||||
// Image-related options
|
||||
hasHostedImages?: boolean;
|
||||
imageCount?: number;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Injectable } from "@nestjs/common";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { EmailHeaderDto } from "../DTO/send-email.dto";
|
||||
import { EmailHeaderKey, Priority } from "../enums/email-header.enum";
|
||||
import { EmailHeaderKey, PriorityEnum } from "../enums/email-header.enum";
|
||||
import {
|
||||
IGenerateAntiThreadingHeadersOptions,
|
||||
IGenerateGmailAntiThreadingHeadersOptions,
|
||||
@@ -36,12 +36,12 @@ export class EmailHeadersService {
|
||||
|
||||
headers.push({
|
||||
key: EmailHeaderKey.XPriority,
|
||||
value: this.getPriorityValue(options.priority || Priority.Normal),
|
||||
value: this.getPriorityValue(options.priority || PriorityEnum.Normal),
|
||||
});
|
||||
|
||||
headers.push({
|
||||
key: EmailHeaderKey.XMSMailPriority,
|
||||
value: options.priority || Priority.Normal,
|
||||
value: options.priority || PriorityEnum.Normal,
|
||||
});
|
||||
|
||||
headers.push({
|
||||
@@ -344,7 +344,7 @@ export class EmailHeadersService {
|
||||
}
|
||||
//****************************************** */
|
||||
|
||||
private getPriorityValue(priority: Priority): string {
|
||||
return priority === Priority.High ? "1" : priority === Priority.Low ? "5" : "3";
|
||||
private getPriorityValue(priority: PriorityEnum): string {
|
||||
return priority === PriorityEnum.High ? "1" : priority === PriorityEnum.Low ? "5" : "3";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { UserRepository } from "../../users/repositories/user.repository";
|
||||
import { BulkActionDto, BulkActionType } from "../DTO/bulk-actions.dto";
|
||||
import { MessageListQueryDto, SearchMessagesQueryDto } from "../DTO/email-query.dto";
|
||||
import { EmailRecipientDto, EmailType, SendEmailDto, UpdateDraftDto } from "../DTO/send-email.dto";
|
||||
import { Priority } from "../enums/email-header.enum";
|
||||
import { PriorityEnum } from "../enums/email-header.enum";
|
||||
import { IEmailMap } from "../interfaces/email-map.interface";
|
||||
|
||||
@Injectable()
|
||||
@@ -108,18 +108,12 @@ export class EmailService {
|
||||
limit: query.limit || 20,
|
||||
page: query.page || 1,
|
||||
order: query.order || "desc",
|
||||
mailbox: query.mailbox || "",
|
||||
query: query.search,
|
||||
from: query.from || query.search,
|
||||
// mailbox: query.mailbox,
|
||||
};
|
||||
delete wildDuckQuery.mailbox;
|
||||
|
||||
if (query.search) {
|
||||
wildDuckQuery.query = query.search;
|
||||
wildDuckQuery.from = query.search;
|
||||
}
|
||||
|
||||
if (query.from) wildDuckQuery.from = query.from;
|
||||
if (query.to) wildDuckQuery.to = query.to;
|
||||
if (query.subject) wildDuckQuery.subject = query.subject;
|
||||
delete wildDuckQuery?.mailbox;
|
||||
|
||||
const response = await firstValueFrom(this.mailServerService.messages.searchMessages(wildduckUserId, wildDuckQuery));
|
||||
|
||||
@@ -727,6 +721,7 @@ export class EmailService {
|
||||
|
||||
//==============================================
|
||||
private transformPaginationQuery(query: MessageListQueryDto): Record<string, unknown> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const transformedQuery: Record<string, any> = { ...query };
|
||||
|
||||
if (!transformedQuery.page) transformedQuery.page = 1;
|
||||
@@ -767,7 +762,7 @@ export class EmailService {
|
||||
businessName,
|
||||
businessDomain,
|
||||
isTransactional: true,
|
||||
priority: Priority.Normal,
|
||||
priority: PriorityEnum.Normal,
|
||||
hasHostedImages: imageDetection.hasHostedImages,
|
||||
imageCount: imageDetection.imageCount,
|
||||
trustedImageSources: imageDetection.trustedImageSources,
|
||||
|
||||
Reference in New Issue
Block a user