Skip to content

Commit

Permalink
Merge pull request #1295 from AletheiaFact/stage
Browse files Browse the repository at this point in the history
Production Release
  • Loading branch information
thesocialdev authored Jul 25, 2024
2 parents 4f7a943 + cd16c0a commit f0007fb
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 60 deletions.
18 changes: 18 additions & 0 deletions server/chat-bot-state/chat-bot-state.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";
import { ChatBotState, ChatBotStateSchema } from "./chat-bot-state.schema";
import { ChatBotStateService } from "./chat-bot-state.service";

const ChatBotStateModel = MongooseModule.forFeature([
{
name: ChatBotState.name,
schema: ChatBotStateSchema,
},
]);

@Module({
imports: [ChatBotStateModel],
exports: [ChatBotStateService],
providers: [ChatBotStateService],
})
export class ChatBotStateModule {}
17 changes: 17 additions & 0 deletions server/chat-bot-state/chat-bot-state.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import * as mongoose from "mongoose";

export type ChatBotStateDocument = ChatBotState & mongoose.Document;

@Schema()
export class ChatBotState {
@Prop({ required: true })
_id: string;

@Prop({ required: true, type: mongoose.Schema.Types.Mixed })
state: any;
}

const ChatBotStateSchemaRaw = SchemaFactory.createForClass(ChatBotState);

export const ChatBotStateSchema = ChatBotStateSchemaRaw;
36 changes: 36 additions & 0 deletions server/chat-bot-state/chat-bot-state.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Injectable } from "@nestjs/common";
import { InjectModel } from "@nestjs/mongoose";
import { Model } from "mongoose";
import { ChatBotState, ChatBotStateDocument } from "./chat-bot-state.schema";

@Injectable()
export class ChatBotStateService {
constructor(
@InjectModel(ChatBotState.name)
private ChatBotStateModel: Model<ChatBotStateDocument>
) {}

async create(state, id: string) {
const newChatBotState = new this.ChatBotStateModel({
_id: id,
state: state,
});
return await newChatBotState.save();
}

async updateState(
id: string,
state: any
): Promise<ChatBotStateDocument | null> {
return await this.ChatBotStateModel.findByIdAndUpdate(
id,
{ state: state },
{ new: true, useFindAndModify: false }
).exec();
}

async getById(id: string) {
const chatBotState = this.ChatBotStateModel.findById(id);
return chatBotState;
}
}
2 changes: 1 addition & 1 deletion server/chat-bot/chat-bot-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const MESSAGES = {
"Desculpe, não entendi sua resposta. Para continuar, preciso que você digite SIM se deseja fazer uma denúncia, ou NÃO se não deseja.\n\nVocê gostaria de fazer uma denúncia agora?",
askForVerificationRequest:
"Por favor, me conte com detalhes o que você gostaria de denunciar.\n\nPor favor, inclua todas as informações que considerar relevantes para que possamos verificar a denúncia de forma eficiente 👀",
thanks: "Muito obrigada por sua contribuição!\n\nSua informação será analisada pela nossa equipe ✅Para saber mais, visite nosso site: https://aletheiafact.org.\n\nDeseja relatar outra denúncia? Responda SIM para continuar ou NÃO para encerrar.",
thanks: "Muito obrigada por sua contribuição!\n\nSua informação será analisada pela nossa equipe ✅Para saber mais, visite nosso site: https://aletheiafact.org.\n\nDeseja relatar outra denúncia? Responda SIM para continuar.",
noTextMessageGreeting:
"Desculpe, só podemos processar mensagens de texto. Por favor, envie sua mensagem em formato de texto.\n\nOlá! Sou o assistente virtual da AletheiaFact.org, estou aqui para ajudá-lo(a) a combater desinformações 🙂 Você gostaria de fazer uma denúncia agora?\n\nResponda SIM para continuar ou NÃO se não deseja denunciar.",
noTextMessageAskForVerificationRequest:
Expand Down
17 changes: 16 additions & 1 deletion server/chat-bot/chat-bot.machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const createChatBotMachine = (
askingForVerificationRequest: {
on: {
RECEIVE_REPORT: {
target: "askingIfVerificationRequest",
target: "finishedReport",
actions: [
"saveVerificationRequest",
"sendThanks",
Expand All @@ -83,6 +83,21 @@ export const createChatBotMachine = (
},
},
},
finishedReport: {
on: {
RECEIVE_YES: {
target: "askingForVerificationRequest",
actions: [
"askForVerificationRequest",
"setResponseMessage",
],
},
ANY_TEXT_MESSAGE: {
target: "askingIfVerificationRequest",
actions: ["sendGreeting", "setResponseMessage"],
},
},
},
sendingNoMessage: {
on: {
ASK_TO_REPORT: {
Expand Down
8 changes: 7 additions & 1 deletion server/chat-bot/chat-bot.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@ import { HttpModule } from "@nestjs/axios";
import { VerificationRequestModule } from "../verification-request/verification-request.module";
import { ConfigModule } from "@nestjs/config";
import { AuthZenviaWebHookMiddleware } from "../middleware/auth-zenvia-webhook.middleware";
import { ChatBotStateModule } from "../chat-bot-state/chat-bot-state.module";

@Module({
imports: [HttpModule, VerificationRequestModule, ConfigModule],
imports: [
HttpModule,
VerificationRequestModule,
ConfigModule,
ChatBotStateModule,
],
providers: [ChatbotService],
controllers: [ChatbotController],
})
Expand Down
166 changes: 109 additions & 57 deletions server/chat-bot/chat-bot.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Observable, throwError } from "rxjs";
import { createChatBotMachine } from "./chat-bot.machine";
import { VerificationRequestService } from "../verification-request/verification-request.service";
import { ConfigService } from "@nestjs/config";
import { ChatBotStateService } from "../chat-bot-state/chat-bot-state.service";

const diacriticsRegex = /[\u0300-\u036f]/g;
const MESSAGE_MAP = {
Expand All @@ -15,98 +16,149 @@ const MESSAGE_MAP = {

@Injectable()
export class ChatbotService {
private chatBotMachineService;

constructor(
private configService: ConfigService,
private readonly httpService: HttpService,
private verificationService: VerificationRequestService
private verificationService: VerificationRequestService,
private chatBotStateService: ChatBotStateService
) {}

onModuleInit() {
this.initializeChatBotMachine();
}
private async getOrCreateChatBotMachine(from: string, channel: string) {
const id = `${channel}-${from}`;
let chatbotState = await this.chatBotStateService.getById(id);

private initializeChatBotMachine() {
this.chatBotMachineService = createChatBotMachine(
this.verificationService
);
this.chatBotMachineService.start();
}
if (!chatbotState) {
const newMachine = createChatBotMachine(this.verificationService);
newMachine.start();
chatbotState = await this.chatBotStateService.create(
newMachine.getSnapshot().value,
id
);
} else {
const rehydratedMachine = createChatBotMachine(
this.verificationService
);
rehydratedMachine.start(chatbotState.state);
chatbotState.state = rehydratedMachine.getSnapshot();
}

//TODO: Find a better way to interpret the user's message.
private normalizeAndLowercase(message: string): string {
return message
.normalize("NFD")
.replace(diacriticsRegex, "")
.toLowerCase();
return chatbotState;
}

private handleMachineEventSend(parsedMessage: string): void {
this.chatBotMachineService.send(
MESSAGE_MAP[parsedMessage] || "NOT_UNDERSTOOD"
private async updateChatBotState(chatbotState) {
await this.chatBotStateService.updateState(
chatbotState._id,
chatbotState.state
);
}

private handleSendingNoMessage(parsedMessage: string): void {
this.chatBotMachineService.send(
parsedMessage === "denuncia" ? "ASK_TO_REPORT" : "RECEIVE_NO"
public async sendMessage(message): Promise<Observable<AxiosResponse<any>>> {
const { api_url, api_token } = this.configService.get("zenvia");
const { from, to, channel, contents } = message;

const chatbotState = await this.getOrCreateChatBotMachine(
from,
channel
);
}
const chatBotMachineService = createChatBotMachine(
this.verificationService
);
chatBotMachineService.start(chatbotState.state);

private handleUserMessage(message): void {
const messageType = message.contents[0].type;
const userMessage = message.contents[0].text;
const userMessage = contents[0].text;
this.handleUserMessage(userMessage, chatBotMachineService);

if (messageType !== "text") {
this.chatBotMachineService.send("NON_TEXT_MESSAGE");
return;
}
const snapshot = chatBotMachineService.getSnapshot();
chatbotState.state = snapshot.value;

const parsedMessage = this.normalizeAndLowercase(userMessage);
const currentState = this.chatBotMachineService.getSnapshot().value;
await this.updateChatBotState(chatbotState);

const responseMessage = snapshot.context.responseMessage;

const body = {
from: to,
to: from,
contents: [{ type: "text", text: responseMessage }],
};

return this.httpService
.post(api_url, body, {
headers: { "X-API-TOKEN": api_token },
})
.pipe(
map((response) => response),
catchError((error) => throwError(() => new Error(error)))
);
}

private handleUserMessage(message: string, chatBotMachineService) {
const parsedMessage = this.normalizeAndLowercase(message);
const currentState = chatBotMachineService.getSnapshot().value;

switch (currentState) {
case "greeting":
this.chatBotMachineService.send("ASK_IF_VERIFICATION_REQUEST");
chatBotMachineService.send("ASK_IF_VERIFICATION_REQUEST");
break;
case "askingIfVerificationRequest":
this.handleMachineEventSend(parsedMessage);
this.handleMachineEventSend(
parsedMessage,
chatBotMachineService
);
break;
case "askingForVerificationRequest":
this.chatBotMachineService.send({
chatBotMachineService.send({
type: "RECEIVE_REPORT",
verificationRequest: userMessage,
verificationRequest: message,
});
break;
case "sendingNoMessage":
this.handleSendingNoMessage(parsedMessage);
this.handleSendingNoMessage(
parsedMessage,
chatBotMachineService
);
break;
case "finishedReport":
this.handleMachineFinishEventSend(
parsedMessage,
chatBotMachineService
);
break;
default:
console.warn(`Unhandled state: ${currentState}`);
}
}

public sendMessage(message): Observable<AxiosResponse<any>> {
const { api_url, api_token } = this.configService.get("zenvia");
this.handleUserMessage(message);
private normalizeAndLowercase(message: string): string {
return message
.normalize("NFD")
.replace(diacriticsRegex, "")
.toLowerCase();
}

const snapshot = this.chatBotMachineService.getSnapshot();
const responseMessage = snapshot.context.responseMessage;
private handleMachineEventSend(
parsedMessage: string,
chatBotMachineService
): void {
chatBotMachineService.send(
MESSAGE_MAP[parsedMessage] || "NOT_UNDERSTOOD"
);
}

const body = {
from: message.to,
to: message.from,
contents: [{ type: "text", text: responseMessage }],
};
private handleMachineFinishEventSend(
parsedMessage: string,
chatBotMachineService
): void {
chatBotMachineService.send(
parsedMessage === "sim" ? "RECEIVE_YES" : "ANY_TEXT_MESSAGE"
);
}

return this.httpService
.post(api_url, body, {
headers: { "X-API-TOKEN": api_token },
})
.pipe(
map((response) => response),
catchError((error) => throwError(() => new Error(error)))
);
private handleSendingNoMessage(
parsedMessage: string,
chatBotMachineService
): void {
chatBotMachineService.send(
parsedMessage === "denuncia" ? "ASK_TO_REPORT" : "RECEIVE_NO"
);
}
}

0 comments on commit f0007fb

Please sign in to comment.