feat: add tts service
This commit is contained in:
@@ -5,17 +5,27 @@ 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 { S3Service } from "../../uploader/providers/s3.service";
|
||||
import { GenerateTemplateDto, TemplatePurposeEnum, TemplateStyleEnum, TemplateThemeEnum } from "../DTO/generate-template.dto";
|
||||
import { SummarizeEmailDto } from "../DTO/summarize-email.dto";
|
||||
import { AudioFormatEnum, SpeedEnum, TextToSpeechDto, VoiceEnum } from "../DTO/text-to-speech.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";
|
||||
import { AITextToSpeechResponse } from "../interfaces/ai-service.interface";
|
||||
import {
|
||||
IEmailData,
|
||||
ITemplateGenerationRequest,
|
||||
SummarizationOptions,
|
||||
TemplateGenerationOptions,
|
||||
TextToSpeechOptions,
|
||||
} from "../interfaces/ai-service.interface";
|
||||
|
||||
@Injectable()
|
||||
export class AiAssistantService {
|
||||
constructor(
|
||||
private readonly messagesService: WildDuckMessagesService,
|
||||
private readonly aiService: AiService,
|
||||
private readonly s3Service: S3Service,
|
||||
) {}
|
||||
|
||||
//************************************************* */
|
||||
@@ -72,6 +82,35 @@ export class AiAssistantService {
|
||||
};
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
async textToSpeechEmailSummary(textToSpeechDto: TextToSpeechDto, userId: string) {
|
||||
const message = await firstValueFrom(this.messagesService.getMessage(userId, textToSpeechDto.mailbox, textToSpeechDto.messageId));
|
||||
|
||||
if (!message) throw new NotFoundException(AiMessage.MESSAGE_NOT_FOUND_OR_ACCESS_DENIED);
|
||||
|
||||
const emailData = this.extractEmailData(message);
|
||||
const summaryResponse = await this.generateEmailSummary(emailData, SummaryLengthEnum.MEDIUM, SummaryToneEnum.PROFESSIONAL);
|
||||
|
||||
// const textForSpeech = this.buildTextForSpeech(summaryResponse, emailData);
|
||||
const textForSpeech = summaryResponse.summary;
|
||||
|
||||
const ttsOptions: TextToSpeechOptions = {
|
||||
voice: textToSpeechDto.voice || VoiceEnum.ALLOY,
|
||||
speed: textToSpeechDto.speed || SpeedEnum.NORMAL,
|
||||
format: textToSpeechDto.format || AudioFormatEnum.MP3,
|
||||
};
|
||||
|
||||
const audioResponse = await this.aiService.textToSpeech(textForSpeech, ttsOptions);
|
||||
|
||||
const fileResponse = await this.saveAudioFile(audioResponse, textToSpeechDto, emailData);
|
||||
|
||||
return {
|
||||
file: fileResponse,
|
||||
summary: summaryResponse,
|
||||
textUsed: textForSpeech,
|
||||
};
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private buildTemplateGenerationOptions(dto: GenerateTemplateDto): TemplateGenerationOptions {
|
||||
return {
|
||||
@@ -176,6 +215,65 @@ export class AiAssistantService {
|
||||
};
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
private async saveAudioFile(audioResponse: AITextToSpeechResponse, textToSpeechDto: TextToSpeechDto, emailData: IEmailData) {
|
||||
const timestamp = Date.now();
|
||||
const sanitizedSubject = emailData.subject?.replace(/[^a-zA-Z0-9]/g, "_").substring(0, 50) || "email_summary";
|
||||
const fileName = `tts_${sanitizedSubject}_${timestamp}.${audioResponse.format}`;
|
||||
|
||||
const s3Key = this.s3Service.generateFileKey(fileName, "audio");
|
||||
|
||||
const s3Result = await this.s3Service.uploadFile(audioResponse.audioBuffer, s3Key, audioResponse.contentType, {
|
||||
originalName: fileName,
|
||||
messageId: textToSpeechDto.messageId.toString(),
|
||||
mailbox: textToSpeechDto.mailbox,
|
||||
voice: textToSpeechDto.voice || VoiceEnum.ALLOY,
|
||||
speed: (textToSpeechDto.speed || SpeedEnum.NORMAL).toString(),
|
||||
format: audioResponse.format,
|
||||
generatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
return {
|
||||
fileUrl: s3Result.url,
|
||||
fileName,
|
||||
format: audioResponse.format,
|
||||
contentType: audioResponse.contentType,
|
||||
fileSize: audioResponse.audioBuffer.length,
|
||||
s3Key: s3Result.key,
|
||||
};
|
||||
}
|
||||
|
||||
//************************************************* */
|
||||
// private buildTextForSpeech(summaryResponse: AISummaryResponse, emailData: IEmailData): string {
|
||||
// let text = `Email Summary: `;
|
||||
|
||||
// if (emailData.subject) {
|
||||
// text += `Subject: ${emailData.subject}. `;
|
||||
// }
|
||||
|
||||
// if (emailData.from) {
|
||||
// text += `From: ${emailData.from}. `;
|
||||
// }
|
||||
|
||||
// if (summaryResponse.summary) {
|
||||
// text += `Summary: ${summaryResponse.summary}. `;
|
||||
// }
|
||||
|
||||
// if (summaryResponse.keyPoints && summaryResponse.keyPoints.length > 0) {
|
||||
// text += `Key Points: ${summaryResponse.keyPoints.join(". ")}. `;
|
||||
// }
|
||||
|
||||
// if (summaryResponse.suggestedActions && summaryResponse.suggestedActions.length > 0) {
|
||||
// text += `Suggested Actions: ${summaryResponse.suggestedActions.join(". ")}. `;
|
||||
// }
|
||||
|
||||
// if (summaryResponse.priority) {
|
||||
// text += `Priority: ${summaryResponse.priority}.`;
|
||||
// }
|
||||
|
||||
// return text;
|
||||
// }
|
||||
|
||||
//************************************************* */
|
||||
private async generateEmailWritingAssistance(writeAssistanceDto: WriteEmailAssistanceDto, _userId: string) {
|
||||
const aiResponse = await this.aiService.assistEmailWriting(writeAssistanceDto.prompt);
|
||||
|
||||
Reference in New Issue
Block a user