Grafos implementado
This commit is contained in:
parent
4ff6ee2e42
commit
53d57e4fa3
Binary file not shown.
141
app/dialog_flow/graph_definition.py
Normal file
141
app/dialog_flow/graph_definition.py
Normal file
@ -0,0 +1,141 @@
|
||||
# Consultme/app/dialog_flow/graph_definition.py
|
||||
|
||||
from typing import Dict, Any, List
|
||||
|
||||
# --- DEFINIÇÃO DOS ESTADOS DO SEU CHATBOT (O GRAFO) ---
|
||||
DIALOG_GRAPH: Dict[str, Dict[str, Any]] = {
|
||||
"INICIO": {
|
||||
"message": "",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"default": "MENU_PRINCIPAL"
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"MENU_PRINCIPAL": {
|
||||
"message": "*Olá você inicou o Consultme!:*",
|
||||
"action_to_perform": "send_main_menu", # Ação para enviar um menu interativo (botões)
|
||||
"expected_input_type": "button_click",
|
||||
"transitions": {
|
||||
"OPTION_AGENDAR": "MENU_PRINCIPAL_STORE",
|
||||
"OPTION_CADASTRO_FLOW": "MENU_PRINCIPAL_STORE",
|
||||
"OPTION_STATUS": "MENU_PRINCIPAL_STORE",
|
||||
"OPTION_FALAR_ATENDENTE": "MENU_PRINCIPAL_STORE",
|
||||
"default": "MENU_PRINCIPAL_STORE" # Volta para o menu se a opção não for reconhecida
|
||||
}
|
||||
},
|
||||
|
||||
"MENU_PRINCIPAL_STORE": {
|
||||
"action_to_perform": "send_main_store", # Ação para enviar um menu interativo (botões)
|
||||
"expected_input_type": "button_click",
|
||||
"transitions": {
|
||||
"OPTION_AGENDAR": "AGENDAMENTO_INICIO",
|
||||
"OPTION_CADASTRO_FLOW": "INICIAR_FLOW_CADASTRO",
|
||||
"OPTION_STATUS": "PEDIR_NUMERO_PEDIDO",
|
||||
"OPTION_FALAR_ATENDENTE": "ENCAMINHAR_ATENDENTE",
|
||||
"default": "MENU_PRINCIPAL" # Volta para o menu se a opção não for reconhecida
|
||||
}
|
||||
},
|
||||
|
||||
"RESPOSTA_NAO_ENTENDIDA": {
|
||||
"message": "Desculpe, não entendi sua última mensagem. Por favor, digite 'menu' para ver as opções ou 'ajuda'.",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"menu": "MENU_PRINCIPAL",
|
||||
"ajuda": "MENU_PRINCIPAL",
|
||||
"default": "RESPOSTA_NAO_ENTENDIDA" # Continua não entendendo
|
||||
}
|
||||
},
|
||||
# --- FLUXO DE CADASTRO (com Flow) ---
|
||||
"INICIAR_FLOW_CADASTRO": {
|
||||
"action_to_perform": "send_flow_cadastro", # Ação para enviar o Flow
|
||||
"flow_id": "COLOQUE_AQUI_O_FLOW_ID_DO_SEU_CADASTRO_PUBLICADO", # ID do seu Flow publicado
|
||||
"flow_cta": "Abrir Cadastro",
|
||||
"expected_input_type": "flow_nfm_reply", # Espera a resposta do Flow
|
||||
"transitions": {
|
||||
"success": "CADASTRO_CONCLUIDO", # Se o Flow for concluído com sucesso
|
||||
"failure": "CADASTRO_FALHA" # Se houver um problema no Flow (ou o usuário não preencher)
|
||||
}
|
||||
},
|
||||
"CADASTRO_CONCLUIDO": {
|
||||
"message": "Obrigado por se cadastrar, ${nome_completo}! Seu e-mail é: ${email}. Já pode explorar nossos serviços!",
|
||||
"action_to_perform": "process_cadastro_data", # Ação para salvar no BD/CRM
|
||||
"expected_input_type": "any", # Qualquer coisa leva ao menu
|
||||
"transitions": {
|
||||
"default": "MENU_PRINCIPAL"
|
||||
}
|
||||
},
|
||||
"CADASTRO_FALHA": {
|
||||
"message": "Não foi possível completar seu cadastro. Por favor, tente novamente ou digite 'ajuda'.",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"default": "MENU_PRINCIPAL"
|
||||
}
|
||||
},
|
||||
# --- FLUXO DE AGENDAMENTO (Exemplo Básico) ---
|
||||
"AGENDAMENTO_INICIO": {
|
||||
"message": "Certo! Para agendar, qual serviço você precisa?",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"default": "AGENDAMENTO_CONFIRMAR_SERVICO"
|
||||
}
|
||||
},
|
||||
"AGENDAMENTO_CONFIRMAR_SERVICO": {
|
||||
"message": "Você precisa de *${servico_agendado}*. Qual data e horário você prefere?",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"default": "AGENDAMENTO_FINALIZAR"
|
||||
},
|
||||
"action_to_perform": "save_temp_service" # Ação para guardar o serviço temporariamente
|
||||
},
|
||||
"AGENDAMENTO_FINALIZAR": {
|
||||
"message": "Seu agendamento para *${servico_agendado}* em *${data_horario_agendado}* foi confirmado! Te vejo lá!",
|
||||
"action_to_perform": "confirm_appointment", # Ação para agendar no sistema real
|
||||
"expected_input_type": "any",
|
||||
"transitions": {
|
||||
"default": "MENU_PRINCIPAL"
|
||||
}
|
||||
},
|
||||
# --- FLUXO DE STATUS DO PEDIDO ---
|
||||
"PEDIR_NUMERO_PEDIDO": {
|
||||
"message": "Por favor, digite o número do seu pedido para consultar o status:",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"default": "CONSULTAR_STATUS_API"
|
||||
}
|
||||
},
|
||||
"CONSULTAR_STATUS_API": {
|
||||
"message": "Consultando o status do pedido *${numero_pedido}*...",
|
||||
"action_to_perform": "call_external_status_api", # Ação para chamar um sistema externo (API futura)
|
||||
"expected_input_type": "api_response", # A resposta vem de uma API, não do usuário
|
||||
"transitions": {
|
||||
"success": "STATUS_EXIBIR",
|
||||
"failure": "STATUS_NAO_ENCONTRADO"
|
||||
}
|
||||
},
|
||||
"STATUS_EXIBIR": {
|
||||
"message": "O status do pedido *${numero_pedido}* é: *${status_retornado}*.",
|
||||
"expected_input_type": "any",
|
||||
"transitions": {
|
||||
"default": "MENU_PRINCIPAL"
|
||||
}
|
||||
},
|
||||
"STATUS_NAO_ENCONTRADO": {
|
||||
"message": "Não consegui encontrar o pedido *${numero_pedido}*. Verifique e digite novamente, ou 'menu'.",
|
||||
"expected_input_type": "text",
|
||||
"transitions": {
|
||||
"menu": "MENU_PRINCIPAL",
|
||||
"default": "PEDIR_NUMERO_PEDIDO"
|
||||
}
|
||||
},
|
||||
# --- FLUXO DE ATENDENTE ---
|
||||
"ENCAMINHAR_ATENDENTE": {
|
||||
"message": "Encaminhando você para um de nossos atendentes. Por favor, aguarde.",
|
||||
"terminal": True # Indica que a conversa termina aqui (até o atendente assumir)
|
||||
},
|
||||
# ... Adicione mais estados conforme a complexidade da sua conversa ...
|
||||
}
|
||||
|
||||
# --- ESTADO INICIAL ---
|
||||
INITIAL_STATE_ID = "INICIO"
|
||||
@ -35,4 +35,4 @@ async def shutdown_event():
|
||||
|
||||
# Exemplo de uso de Uvicorn para rodar o FastAPI
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=5000)
|
||||
uvicorn.run(app, host="0.0.0.0", port=5000)
|
||||
|
||||
Binary file not shown.
@ -36,6 +36,10 @@ from cryptography.hazmat.backends import default_backend
|
||||
|
||||
from config import WHATSAPP_ACCESS_TOKEN, WHATSAPP_PHONE_NUMBER_ID, VERIFY_TOKEN, FLOW_PRIVATE_KEY_PASSWORD # <-- ADICIONADO
|
||||
# Importe Message para type hinting. Certifique-se que webhook_model.py está correto primeiro!
|
||||
# Certifique-se de importar o grafo e o estado inicial
|
||||
from dialog_flow.graph_definition import DIALOG_GRAPH, INITIAL_STATE_ID
|
||||
# Certifique-se de que FlowStatusChangeEvent também está importado se ainda não estiver
|
||||
|
||||
from models.webhook_model import Message, TextMessage, ButtonMessage, InteractiveMessage, ListReply # Adicione os modelos necessários
|
||||
|
||||
FLOW_PRIVATE_KEY_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../private.pem')
|
||||
@ -254,88 +258,254 @@ def start_or_update_session(sender_id: str):
|
||||
|
||||
|
||||
|
||||
#Fluxo de mensagens:
|
||||
|
||||
# --- NOVA FUNÇÃO: O MOTOR DO CHATBOT BASEADO EM GRAFO ---
|
||||
async def process_user_input_with_graph(sender_id: str, message_type: str, message_content: Any):
|
||||
"""
|
||||
Processa a entrada do usuário e avança o estado da conversa no grafo.
|
||||
"""
|
||||
session_data = get_session_state(sender_id)
|
||||
if not session_data:
|
||||
# Se a sessão não existe (timeout ou novo usuário), inicie do estado INICIO
|
||||
current_state_id = INITIAL_STATE_ID
|
||||
start_or_update_session(sender_id) # Garante que a sessão existe
|
||||
session_data = get_session_state(sender_id) # Recarrega para ter certeza
|
||||
else:
|
||||
current_state_id = session_data.get("current_state", INITIAL_STATE_ID) # Pega o estado atual
|
||||
|
||||
current_state = DIALOG_GRAPH.get(current_state_id)
|
||||
if not current_state:
|
||||
print(f"❌ ERRO GRAVE: Estado '{current_state_id}' não encontrado no DIALOG_GRAPH. Retornando ao INICIO.")
|
||||
current_state_id = INITIAL_STATE_ID
|
||||
current_state = DIALOG_GRAPH[INITIAL_STATE_ID]
|
||||
session_data["current_state"] = INITIAL_STATE_ID # Reseta o estado na sessão
|
||||
|
||||
print(f"DEBUG_GRAPH: Usuário {sender_id} no estado '{current_state_id}'. Tipo de entrada: {message_type}. Conteúdo: {message_content}")
|
||||
|
||||
next_state_id = None
|
||||
user_input_processed = str(message_content).lower().strip() # Padroniza a entrada para transições
|
||||
|
||||
# --- Lógica para determinar o próximo estado (transição) ---
|
||||
if message_type == "text":
|
||||
# Verifica se alguma palavra-chave da transição corresponde ao texto
|
||||
for keyword, target_state in current_state.get("transitions", {}).items():
|
||||
if keyword != "default" and keyword in user_input_processed:
|
||||
next_state_id = target_state
|
||||
break
|
||||
if not next_state_id: # Se nenhuma palavra-chave específica, usa o default
|
||||
next_state_id = current_state.get("transitions", {}).get("default", INITIAL_STATE_ID)
|
||||
|
||||
session_data["last_text_input"] = message_content # Salva a última entrada de texto para uso futuro
|
||||
|
||||
elif message_type == "button_click":
|
||||
# O message_content já é o payload do botão
|
||||
next_state_id = current_state.get("transitions", {}).get(message_content, INITIAL_STATE_ID)
|
||||
|
||||
elif message_type == "flow_nfm_reply":
|
||||
# Quando uma resposta de Flow chega, o next_state depende do sucesso/falha do Flow
|
||||
# Assumimos que handle_flow_response já processou o Flow e determinou sucesso/falha
|
||||
# A lógica para a transição do Flow precisa ser mais sofisticada.
|
||||
# Por enquanto, vamos para o estado de conclusão do Flow.
|
||||
next_state_id = current_state.get("transitions", {}).get("success", INITIAL_STATE_ID) # Por padrão, vai para o sucesso
|
||||
session_data["flow_data"] = message_content # Salva os dados do Flow para processamento posterior
|
||||
|
||||
# ... (adicionar outros tipos de entrada, como "api_response" para transições internas, se necessário) ...
|
||||
|
||||
# Atualiza o estado da sessão
|
||||
session_data["current_state"] = next_state_id
|
||||
start_or_update_session(sender_id) # Atualiza o timestamp da sessão
|
||||
|
||||
print(f"DEBUG_GRAPH: Usuário {sender_id} transicionou para o estado: '{next_state_id}'")
|
||||
await execute_state_action_and_respond(sender_id, next_state_id, session_data)
|
||||
|
||||
|
||||
async def execute_state_action_and_respond(sender_id: str, state_id: str, session_data: Dict[str, Any]):
|
||||
"""
|
||||
Executa a ação de um estado e envia a resposta correspondente.
|
||||
"""
|
||||
state_definition = DIALOG_GRAPH.get(state_id)
|
||||
if not state_definition:
|
||||
print(f"❌ ERRO GRAVE: Definição para o estado '{state_id}' não encontrada. Não é possível responder.")
|
||||
await send_text_message(sender_id, "Ops! Ocorreu um erro interno. Por favor, tente novamente.")
|
||||
session_data["current_state"] = INITIAL_STATE_ID # Reseta o estado
|
||||
start_or_update_session(sender_id)
|
||||
return
|
||||
|
||||
# --- Ações a serem realizadas pelo bot no novo estado ---
|
||||
if "action_to_perform" in state_definition:
|
||||
action = state_definition["action_to_perform"]
|
||||
print(f"DEBUG_GRAPH: Executando ação para estado '{state_id}': {action}")
|
||||
|
||||
if action == "send_main_menu":
|
||||
await send_time_menu(sender_id)
|
||||
elif action == "send_main_store":
|
||||
await send_store_menu(sender_id)
|
||||
elif action == "send_flow_cadastro":
|
||||
flow_id = state_definition.get("flow_id")
|
||||
flow_cta = state_definition.get("flow_cta")
|
||||
if flow_id:
|
||||
# O Flow ID real virá da DIALOG_GRAPH, não do SendFlowRequest
|
||||
await send_whatsapp_flow(sender_id, flow_id, flow_cta)
|
||||
else:
|
||||
await send_text_message(sender_id, "Ops! O Flow de cadastro não está configurado. Tente novamente mais tarde.")
|
||||
session_data["current_state"] = INITIAL_STATE_ID
|
||||
start_or_update_session(sender_id)
|
||||
|
||||
elif action == "process_cadastro_data":
|
||||
# Aqui você processaria os dados do Flow que estão em session_data["flow_data"]
|
||||
flow_data = json.loads(session_data.get("flow_data", "{}")) # Carregar os dados salvos
|
||||
nome_completo = flow_data.get("nome_completo")
|
||||
email = flow_data.get("email")
|
||||
|
||||
if nome_completo and email:
|
||||
print(f"DEBUG_GRAPH: Processando cadastro para {nome_completo}, {email}")
|
||||
# AQUI você salvaria no BD/CRM
|
||||
# salvar_usuario_no_banco_de_dados(sender_id, nome_completo, email)
|
||||
# enviar_para_crm(nome_completo, email)
|
||||
response_message = state_definition["message"].replace("${nome_completo}", nome_completo).replace("${email}", email)
|
||||
await send_text_message(sender_id, response_message)
|
||||
else:
|
||||
await send_text_message(sender_id, "Falha ao processar cadastro. Dados incompletos. Por favor, tente novamente.")
|
||||
session_data["current_state"] = DIALOG_GRAPH.get("CADASTRO_FALHA", {}).get("transitions",{}).get("default", INITIAL_STATE_ID) # Ir para estado de falha
|
||||
start_or_update_session(sender_id)
|
||||
|
||||
|
||||
elif action == "save_temp_service":
|
||||
# Exemplo: guardar o serviço agendado
|
||||
# service = session_data.get("last_text_input") # Pega o último texto que o usuário enviou
|
||||
# session_data["servico_agendado"] = service
|
||||
# start_or_update_session(sender_id) # Atualiza a sessão
|
||||
pass # Ação interna, não envia mensagem aqui
|
||||
|
||||
elif action == "confirm_appointment":
|
||||
# Exemplo: confirmar agendamento no sistema externo
|
||||
# (Chamada de API para sistema de agendamento)
|
||||
pass # Ação interna, a mensagem já vem do state_definition
|
||||
|
||||
|
||||
elif action == "call_external_status_api":
|
||||
# AQUI é onde você faria a chamada para o seu sistema externo
|
||||
# Por enquanto, apenas um placeholder
|
||||
# numero_pedido = session_data.get("last_text_input")
|
||||
# print(f"DEBUG_GRAPH: Chamando API externa para pedido {numero_pedido}")
|
||||
# try:
|
||||
# status_api = await external_api_call(numero_pedido)
|
||||
# session_data["status_retornado"] = status_api # Salva na sessão
|
||||
# # Transicionar internamente para STATUS_EXIBIR ou STATUS_NAO_ENCONTRADO
|
||||
# await execute_state_action_and_respond(sender_id, "STATUS_EXIBIR", session_data) # Transição interna
|
||||
# return
|
||||
# except Exception:
|
||||
# await execute_state_action_and_respond(sender_id, "STATUS_NAO_ENCONTRADO", session_data) # Transição interna
|
||||
# return
|
||||
pass # Implementação futura, a mensagem vem do estado.
|
||||
|
||||
# --- Enviar a mensagem do novo estado (se houver) ---
|
||||
if "message" in state_definition:
|
||||
# Substitui placeholders na mensagem
|
||||
final_message = state_definition["message"]
|
||||
if "${nome_completo}" in final_message and session_data.get("flow_data"):
|
||||
flow_data = json.loads(session_data["flow_data"])
|
||||
final_message = final_message.replace("${nome_completo}", flow_data.get("nome_completo", "usuário"))
|
||||
if "${email}" in final_message and session_data.get("flow_data"):
|
||||
flow_data = json.loads(session_data["flow_data"])
|
||||
final_message = final_message.replace("${email}", flow_data.get("email", "não informado"))
|
||||
|
||||
# Para outros placeholders como ${servico_agendado}, ${data_horario_agendado}, ${numero_pedido}, ${status_retornado}
|
||||
# você faria substituições semelhantes baseadas em session_data
|
||||
|
||||
await send_text_message(sender_id, final_message)
|
||||
|
||||
# Lógica para limpar a sessão se o estado for terminal
|
||||
if state_definition.get("terminal"):
|
||||
await clean_session_and_notify(sender_id, send_timeout_message=False) # Não envia msg de timeout
|
||||
print(f"DEBUG_GRAPH: Sessão para {sender_id} encerrada no estado terminal '{state_id}'.")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# --- Lógicas de Tratamento de Mensagens Recebidas (Funções Auxiliares) ---
|
||||
# Estas funções contêm a lógica de como seu bot irá interagir.
|
||||
|
||||
# Esta função precisa ser definida no arquivo!
|
||||
# --- handle_message_type: Agora é o ponto de entrada principal que chama o motor do grafo ---
|
||||
#--- FUNÇÃO handle_message_type: A VERSÃO CORRETA PARA O TIMEOUT E GRAFO ---
|
||||
async def handle_message_type(message: Message):
|
||||
sender_id = message.from_
|
||||
|
||||
# 1. Iniciar ou atualizar a sessão para o remetente atual
|
||||
# Isso vai automaticamente agendar/reagendar a limpeza.
|
||||
start_or_update_session(sender_id) # Esta função agora agenda a limpeza!
|
||||
# 1. Limpar sessões inativas
|
||||
# Esta função limpa as sessões que expiraram e envia a mensagem de timeout via scheduler.
|
||||
start_or_update_session(sender_id) # Esta é a que precisa ser assíncrona
|
||||
|
||||
# 2. Recuperar o estado da sessão (se houver, para uso futuro com grafos)
|
||||
# 2. INICIAR OU ATUALIZAR A SESSÃO PARA O REMETENTE ATUAL.
|
||||
# ISSO É ESSENCIAL PARA O TIMEOUT. Se o usuário mandar mensagem, a sessão dele é atualizada
|
||||
# e o agendamento de timeout é resetado.
|
||||
start_or_update_session(sender_id)
|
||||
|
||||
# 3. Recuperar o estado da sessão (já atualizado)
|
||||
session_data = get_session_state(sender_id)
|
||||
if session_data:
|
||||
print(f"DEBUG_SESSION: Estado atual da sessão para {sender_id}: {session_data['current_state']}")
|
||||
else:
|
||||
# Isso não deveria acontecer se start_or_update_session funcionou
|
||||
print(f"DEBUG_SESSION: Sessão para {sender_id} não encontrada após start_or_update_session (possível erro).")
|
||||
print(f"DEBUG_SESSION: ERRO: Sessão para {sender_id} não encontrada após atualização. Isso é inesperado.")
|
||||
# Se por algum motivo não tiver sessão, podemos resetar para o estado inicial
|
||||
start_or_update_session(sender_id)
|
||||
session_data = get_session_state(sender_id) # Tenta novamente
|
||||
|
||||
|
||||
message_content = None
|
||||
message_type = None
|
||||
|
||||
# --- IDENTIFICAÇÃO DO TIPO DE MENSAGEM ---
|
||||
# Esta parte permanece como estava, identificando o tipo e conteúdo.
|
||||
if message.type == 'text' and message.text:
|
||||
await handle_text_message(sender_id, message.text.body) # Passe sender_id
|
||||
elif message.type == 'button' and message.button:
|
||||
await handle_button_response(sender_id, message.button.payload) # Passe sender_id
|
||||
message_type = "text"
|
||||
message_content = message.text.body
|
||||
elif message.type == 'button' and message.button: # Resposta de botão de resposta rápida
|
||||
message_type = "button_click"
|
||||
message_content = message.button.payload
|
||||
elif message.type == 'interactive' and message.interactive:
|
||||
if message.interactive.type == 'list_reply' and message.interactive.list_reply:
|
||||
await handle_list_response(sender_id, message.interactive.list_reply.id, message.interactive.list_reply.title) # Passe sender_id
|
||||
elif message.interactive.type == 'button_reply' and message.interactive.button_reply:
|
||||
await handle_button_response(sender_id, message.interactive.button_reply.payload) # Passe sender_id
|
||||
if message.interactive.type == 'list_reply' and message.interactive.list_reply: # Resposta de lista
|
||||
message_type = "list_reply"
|
||||
message_content = message.interactive.list_reply.id
|
||||
elif message.interactive.type == 'button_reply' and message.interactive.button_reply: # Resposta de botão interativo
|
||||
message_type = "button_click" # Tratar como clique de botão
|
||||
message_content = message.interactive.button_reply.id
|
||||
elif message.interactive.type == 'nfm_reply' and message.interactive.nfm_reply: # Resposta de Flow
|
||||
message_type = "flow_nfm_reply"
|
||||
message_content = message.interactive.nfm_reply.response_json
|
||||
else:
|
||||
print(f" Tipo interativo desconhecido recebido: {message.interactive.type}")
|
||||
await send_text_message(sender_id, "Desculpe, não entendi essa interação interativa.")
|
||||
elif message.type == 'image':
|
||||
print(' Recebi uma imagem!')
|
||||
await send_text_message(sender_id, "Que legal! Recebi sua imagem. No momento, só consigo processar texto e interações.")
|
||||
else:
|
||||
print(f" Tipo de mensagem não suportado: {message.type}")
|
||||
return # Sai, não há transição no grafo para isso
|
||||
elif message.type == 'image': # Mensagens que não são processadas pelo grafo
|
||||
message_type = "image"
|
||||
message_content = "imagem_recebida" # Placeholder
|
||||
await send_text_message(sender_id, "Recebi sua imagem. No momento, só consigo processar texto e interações.")
|
||||
return # Sai, não há transição no grafo para isso
|
||||
else: # Tipo de mensagem não suportado ou desconhecido
|
||||
message_type = "unsupported"
|
||||
message_content = "tipo_desconhecido"
|
||||
await send_text_message(sender_id, "Desculpe, não entendi o tipo de mensagem que você enviou.")
|
||||
return # Sai
|
||||
|
||||
# 4. O motor do grafo processa a entrada e gerencia o estado da sessão.
|
||||
# A `session_data` já foi atualizada no passo 2.
|
||||
await process_user_input_with_graph(sender_id, message_type, message_content)
|
||||
|
||||
async def handle_text_message(sender_id: str, text: str):
|
||||
lower_text = text.lower()
|
||||
if lower_text != "" :
|
||||
await send_text_message(sender_id, "Olá! Você iniciou o Consultme. Escolha uma das opções de consulta no menu abaixo:")
|
||||
await send_interactive_menu(sender_id) # Chama a função para enviar um menu interativo
|
||||
elif 'menu' in lower_text:
|
||||
await send_interactive_menu(sender_id)
|
||||
elif 'cadastro' in lower_text:
|
||||
await send_text_message(sender_id, "Para iniciar seu cadastro, por favor, me diga seu nome completo:")
|
||||
# Implemente lógica para salvar o estado do usuário aqui (ex: em um DB)
|
||||
elif 'ajuda' in lower_text:
|
||||
await send_text_message(sender_id, "Posso te guiar com as funcionalidades principais. Escolha uma opção do menu ou digite uma pergunta.")
|
||||
await send_interactive_menu(sender_id)
|
||||
else:
|
||||
await send_text_message(sender_id, f"Recebi sua mensagem: \"{text}\". Parece que não entendi bem. Você pode digitar 'menu' para ver as opções disponíveis ou 'ajuda'.")
|
||||
|
||||
async def handle_button_response(sender_id: str, payload: str):
|
||||
response_text = ''
|
||||
if payload == 'OPTION_AGENDAR':
|
||||
response_text = "Certo! Para agendar um serviço, qual serviço você precisa e a data/hora preferida?"
|
||||
elif payload == 'OPTION_STATUS':
|
||||
response_text = "Para verificar o status de seu pedido, informe o número do seu pedido."
|
||||
elif payload == 'OPTION_FALAR_ATENDENTE':
|
||||
response_text = "Encaminhando você para um de nossos atendentes. Aguarde, por favor."
|
||||
else:
|
||||
response_text = "Não entendi a opção de botão selecionada. Tente novamente ou digite 'menu'."
|
||||
await send_text_message(sender_id, response_text)
|
||||
|
||||
async def handle_list_response(sender_id: str, list_id: str, list_title: str):
|
||||
response_text = ''
|
||||
if list_id == 'item_reparo_geral':
|
||||
response_text = f"Você selecionou \"{list_title}\". Qual o problema específico que você precisa de reparo?"
|
||||
elif list_id == 'item_instalacao':
|
||||
response_text = f"Você selecionou \"{list_title}\". Qual tipo de instalação você precisa?"
|
||||
elif list_id == 'item_duvidas':
|
||||
response_text = f"Você selecionou \"{list_title}\". Por favor, digite sua pergunta."
|
||||
elif list_id == 'item_reclamacoes':
|
||||
response_text = f"Você selecionou \"{list_title}\". Por favor, descreva o problema em detalhes."
|
||||
else:
|
||||
response_text = "Opção de lista não reconhecida. Tente novamente ou digite 'menu'."
|
||||
await send_text_message(sender_id, response_text)
|
||||
|
||||
# --- Funções de Envio de Mensagens para o WhatsApp ---
|
||||
# Estas funções fazem as chamadas à API da Meta para enviar mensagens.
|
||||
@ -449,8 +619,85 @@ async def mark_message_as_read(message_id: str):
|
||||
except httpx.RequestError as e:
|
||||
print(f"❌ Erro de rede ao marcar mensagem {message_id} como lida: {e}")
|
||||
|
||||
|
||||
|
||||
# --- Função de Exemplo para Enviar um Menu Interativo ---
|
||||
async def send_interactive_menu(to: str):
|
||||
# --- NOVA FUNÇÃO: send_stores_list_menu (Para menu de LISTA de lojas) ---
|
||||
async def send_time_menu(to: str): # <-- FUNÇÃO QUE VOCÊ PEDIU
|
||||
"""
|
||||
Envia uma mensagem interativa de LISTA para o usuário com opções de lojas.
|
||||
"""
|
||||
header_text = "Escolha o Período que você quer visualizar o indicador"
|
||||
body_text = "Veja as opções a baixo"
|
||||
button_title = "Clique aqui" # Texto do botão que o usuário clica para ABRIR a lista
|
||||
|
||||
sections = [
|
||||
{
|
||||
"title": "Acumulados", # Título da primeira seção
|
||||
"rows": [
|
||||
{"id": 'OPTION_ANO', "title": 'Acumulado do Ano', "description": "Do do ano até ontem."},
|
||||
{"id": 'OPTION_MES', "title": 'Acumulado do Mês', "description": "Do primeira do mês até ontem."},
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Resultados", # Título da segunda seção
|
||||
"rows": [
|
||||
{"id": 'OPTION_ONTEM', "title": 'Resultado de Ontem', "description": "Ver o realizado de ontem."},
|
||||
{"id": 'OPTION_HOJE', "title": 'Resultado de Hoje', "description": "Resultado Parcial de Hoje"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Sair", # Título da segunda seção
|
||||
"rows": [
|
||||
{"id": 'OPTION_SAIR', "title": 'Sair', "description": "Encerre a conversa"},
|
||||
]
|
||||
}
|
||||
]
|
||||
# Relembrando: até 10 seções e 10 itens por seção (total 100 itens).
|
||||
|
||||
# CHAMA send_interactive_list DIRETAMENTE AQUI
|
||||
await send_interactive_list(to, header_text, body_text, button_title, sections)
|
||||
|
||||
|
||||
async def send_store_menu(to: str): # <-- FUNÇÃO QUE VOCÊ PEDIU
|
||||
"""
|
||||
Envia uma mensagem interativa de LISTA para o usuário com opções de lojas.
|
||||
"""
|
||||
header_text = "Escolha o Período que você quer visualizar o indicador"
|
||||
body_text = "Veja as opções a baixo"
|
||||
button_title = "Clique aqui" # Texto do botão que o usuário clica para ABRIR a lista
|
||||
|
||||
sections = [
|
||||
{
|
||||
"title": "Acumulados", # Título da primeira seção
|
||||
"rows": [
|
||||
{"id": 'OPTION_ANO', "title": 'Total do CP', "description": "Visualize o resultado Total do CP"},
|
||||
{"id": 'OPTION_MES', "title": 'Total do Estado', "description": "Visualize o resultado Total das suas Lojas"},
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Por Loja", # Título da segunda seção
|
||||
"rows": [
|
||||
{"id": 'OPTION_ONTEM', "title": 'Total das suas Lojas', "description": "Visualize o total das suas Lojas"},
|
||||
{"id": 'OPTION_HOJE', "title": 'Total de uma Loja', "description": "Visualize o total de uma loja."}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Sair", # Título da segunda seção
|
||||
"rows": [
|
||||
{"id": 'OPTION_SAIR', "title": 'Sair', "description": "Encerre a conversa"},
|
||||
]
|
||||
}
|
||||
]
|
||||
# Relembrando: até 10 seções e 10 itens por seção (total 100 itens).
|
||||
|
||||
# CHAMA send_interactive_list DIRETAMENTE AQUI
|
||||
await send_interactive_list(to, header_text, body_text, button_title, sections)
|
||||
|
||||
# Exemplo de como usar o MENU, só aceita 3 opções mas já aparece direto pro usuário as opções.
|
||||
|
||||
'''
|
||||
async def send_stores_menu(to: str):
|
||||
buttons = [
|
||||
{"id": 'OPTION_AGENDAR', "title": 'Total do CP'},
|
||||
{"id": 'OPTION_STATUS', "title": 'Total das suas Lojas'},
|
||||
@ -458,7 +705,17 @@ async def send_interactive_menu(to: str):
|
||||
]
|
||||
await send_interactive_buttons(to, "Menu Principal", "Escolha a Dimensão das Lojas que você quer visulizar o indicador:", buttons)
|
||||
|
||||
async def send_time_menu(to: str):
|
||||
buttons = [
|
||||
{"id": 'OPTION_AGENDAR', "title": 'Acumulado do Ano'},
|
||||
{"id": 'OPTION_STATUS', "title": 'Acumulado do Mês'},
|
||||
{"id": 'OPTION_FALAR_ATENDENTE', "title": 'Resultado de Ontem'}
|
||||
#{"id": 'OPTION_FALAR_ATENDENTE', "title": 'Resultado Parcial de Hoje'},
|
||||
#{"id": 'OPTION_FALAR_ATENDENTE', "title": 'Sair'}
|
||||
]
|
||||
await send_interactive_buttons(to, "Escolha o Período que você quer visualizar o indicador", "Veja as opções abaixo:", buttons)
|
||||
|
||||
'''
|
||||
async def send_whatsapp_flow(to: str, flow_id: str, flow_cta: str, screen: str = "welcome_screen") -> Dict[str, Any]:
|
||||
url = f"https://graph.facebook.com/v18.0/{WHATSAPP_PHONE_NUMBER_ID}/messages"
|
||||
headers = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user