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