/**
* Chatbot Service
* Provides support responses and FAQ handling
*/
import { SupportFAQ, ChatbotResponse } from '../types/index.js';
import { logger } from '../utils/logger.js';
import { OpenAIService } from './openai.service.js';
export class ChatbotService {
private faqs: SupportFAQ[];
private readonly openAIService?: OpenAIService;
constructor(openAIService?: OpenAIService) {
this.faqs = this.initializeFAQs();
this.openAIService = openAIService;
}
/**
* Initialize FAQ database
*/
private initializeFAQs(): SupportFAQ[] {
return [
// Account & Registration
{
question: 'Como criar uma conta no FitSlot?',
answer: 'Para criar uma conta, acesse o aplicativo ou website, clique em "Registrar" e preencha seus dados pessoais. Você receberá um email de confirmação.',
category: 'conta',
keywords: ['criar', 'conta', 'registrar', 'cadastro', 'sign up']
},
{
question: 'Como recuperar minha senha?',
answer: 'Na tela de login, clique em "Esqueci minha senha", digite seu email cadastrado e siga as instruções enviadas por email.',
category: 'conta',
keywords: ['senha', 'recuperar', 'esqueci', 'password', 'reset']
},
{
question: 'Como atualizar meus dados cadastrais?',
answer: 'Acesse o menu "Perfil" no aplicativo, clique em "Editar Dados" e atualize as informações desejadas. Não esqueça de salvar as alterações.',
category: 'conta',
keywords: ['atualizar', 'dados', 'perfil', 'editar', 'update']
},
// Scheduling
{
question: 'Como agendar um horário?',
answer: 'Vá para "Agendamentos", selecione o serviço desejado, escolha data e horário disponível, e confirme o agendamento. Você receberá uma confirmação.',
category: 'agendamento',
keywords: ['agendar', 'marcar', 'horário', 'scheduling', 'appointment']
},
{
question: 'Como cancelar um agendamento?',
answer: 'Acesse "Meus Agendamentos", selecione o agendamento que deseja cancelar e clique em "Cancelar". É possível cancelar até 24 horas antes.',
category: 'agendamento',
keywords: ['cancelar', 'desmarcar', 'agendamento', 'cancel']
},
{
question: 'Como reagendar um horário?',
answer: 'Você pode cancelar o agendamento atual e criar um novo, ou entrar em contato com o suporte para reagendar mantendo sua prioridade.',
category: 'agendamento',
keywords: ['reagendar', 'remarcar', 'trocar', 'reschedule']
},
// Bioimpedance
{
question: 'O que é bioimpedância?',
answer: 'Bioimpedância é um exame não invasivo que avalia a composição corporal, medindo percentual de gordura, massa muscular, água corporal e outros indicadores.',
category: 'bioimpedancia',
keywords: ['bioimpedância', 'bioimpedance', 'composição', 'corporal', 'exame']
},
{
question: 'Como me preparar para o exame de bioimpedância?',
answer: 'Esteja em jejum de 4 horas, evite exercícios intensos 24h antes, esvazie a bexiga antes do exame, e evite consumo de álcool e cafeína no dia anterior.',
category: 'bioimpedancia',
keywords: ['preparar', 'jejum', 'bioimpedância', 'exame', 'preparation']
},
{
question: 'Como visualizar meus resultados de bioimpedância?',
answer: 'Os resultados ficam disponíveis em "Meus Exames" após processamento. Você pode baixar o PDF ou visualizar online com as análises detalhadas.',
category: 'bioimpedancia',
keywords: ['resultado', 'visualizar', 'exame', 'pdf', 'results']
},
// Support & Technical
{
question: 'O aplicativo não está funcionando, o que fazer?',
answer: 'Primeiro, tente fechar e abrir o app novamente. Se persistir, verifique sua conexão de internet, atualize o app para última versão ou reinstale.',
category: 'suporte',
keywords: ['não funciona', 'erro', 'problema', 'bug', 'error']
},
{
question: 'Como entrar em contato com o suporte?',
answer: 'Você pode abrir um ticket através do menu "Suporte" no app, enviar email para suporte@fitslot.com, ou ligar para (11) 1234-5678.',
category: 'suporte',
keywords: ['contato', 'suporte', 'ajuda', 'contact', 'support', 'help']
},
// Payment & Plans
{
question: 'Quais são as formas de pagamento aceitas?',
answer: 'Aceitamos cartão de crédito, débito, PIX, boleto bancário e pagamento através da plataforma. Consulte as taxas específicas de cada serviço.',
category: 'pagamento',
keywords: ['pagamento', 'pagar', 'payment', 'cartão', 'pix', 'boleto']
},
{
question: 'Como solicitar reembolso?',
answer: 'Para solicitar reembolso, acesse "Meus Pagamentos", selecione a transação e clique em "Solicitar Reembolso". Analisaremos em até 5 dias úteis.',
category: 'pagamento',
keywords: ['reembolso', 'devolução', 'refund', 'estorno']
}
];
}
/**
* Search for relevant FAQs based on query
*/
searchFAQs(query: string, limit: number = 5): SupportFAQ[] {
const normalizedQuery = query.toLowerCase().trim();
// Score each FAQ based on relevance
const scoredFAQs = this.faqs.map(faq => {
let score = 0;
// Check if query matches keywords
for (const keyword of faq.keywords) {
if (normalizedQuery.includes(keyword.toLowerCase())) {
score += 10;
}
}
// Check if query matches question or answer
if (faq.question.toLowerCase().includes(normalizedQuery)) {
score += 20;
}
if (faq.answer.toLowerCase().includes(normalizedQuery)) {
score += 5;
}
// Check individual words
const queryWords = normalizedQuery.split(/\s+/);
for (const word of queryWords) {
if (word.length > 3) { // Ignore short words
if (faq.question.toLowerCase().includes(word)) {
score += 3;
}
if (faq.keywords.some(k => k.toLowerCase().includes(word))) {
score += 2;
}
}
}
return { faq, score };
});
// Sort by score and return top results
return scoredFAQs
.filter(item => item.score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, limit)
.map(item => item.faq);
}
/**
* Get chatbot response for a user query
*/
async getChatbotResponse(query: string): Promise<ChatbotResponse> {
logger.info('Processing chatbot query', { query });
const relatedFAQs = this.searchFAQs(query, 3);
let message: string;
const suggestedActions: string[] = [];
if (relatedFAQs.length > 0) {
// Found relevant FAQs
message = 'Encontrei algumas informações que podem ajudar você:';
suggestedActions.push('Ver FAQs relacionadas');
// Add context-specific suggestions
const categories = new Set(relatedFAQs.map(faq => faq.category));
if (categories.has('agendamento')) {
suggestedActions.push('Criar novo agendamento');
suggestedActions.push('Ver meus agendamentos');
}
if (categories.has('conta')) {
suggestedActions.push('Editar perfil');
}
if (categories.has('bioimpedancia')) {
suggestedActions.push('Ver meus exames');
suggestedActions.push('Agendar bioimpedância');
}
if (categories.has('suporte')) {
suggestedActions.push('Abrir ticket de suporte');
}
} else {
// No relevant FAQs found
message = 'Não encontrei uma resposta específica para sua pergunta. Posso ajudar você de outras formas:';
suggestedActions.push(
'Abrir ticket de suporte',
'Falar com atendente',
'Ver todas as FAQs',
'Buscar na central de ajuda'
);
}
const fallbackResponse: ChatbotResponse = {
message,
relatedFAQs: relatedFAQs.length > 0 ? relatedFAQs : undefined,
suggestedActions
};
logger.debug('Chatbot fallback response generated', {
relatedFAQsCount: relatedFAQs.length,
suggestedActionsCount: suggestedActions.length
});
if (!this.openAIService?.isConfigured()) {
return fallbackResponse;
}
try {
const aiMessage = await this.openAIService.generateSupportAnswer({
query,
relatedFAQs,
suggestedActions
});
if (aiMessage) {
logger.debug('Chatbot response enhanced with OpenAI');
return {
...fallbackResponse,
message: aiMessage
};
}
} catch (error) {
logger.warn('Failed to enhance chatbot response with OpenAI', error);
}
return fallbackResponse;
}
/**
* Get FAQs by category
*/
getFAQsByCategory(category: string): SupportFAQ[] {
return this.faqs.filter(faq =>
faq.category.toLowerCase() === category.toLowerCase()
);
}
/**
* Get all FAQ categories
*/
getCategories(): string[] {
return [...new Set(this.faqs.map(faq => faq.category))];
}
/**
* Get all FAQs
*/
getAllFAQs(): SupportFAQ[] {
return this.faqs;
}
}