chatgraph 0.2.6__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of chatgraph might be problematic. Click here for more details.

chatgraph/__init__.py CHANGED
@@ -1,21 +1,22 @@
1
1
  from .auth.credentials import Credential
2
2
  from .bot.chatbot_model import ChatbotApp
3
3
  from .bot.chatbot_router import ChatbotRouter
4
- from .messages.rabbitMQ_message_consumer import RabbitMessageConsumer
5
- from .types.message_types import Message, UserState
4
+ from .messages.message_consumer import MessageConsumer
5
+ from .types.message_types import UserCall, UserState, Element
6
6
  from .types.output_state import ChatbotResponse, RedirectResponse, EndChatResponse, TransferToHuman
7
7
  from .types.route import Route
8
8
 
9
9
  __all__ = [
10
10
  'ChatbotApp',
11
11
  'Credential',
12
- 'Message',
12
+ 'UserCall',
13
13
  'ChatbotRouter',
14
14
  'ChatbotResponse',
15
15
  'RedirectResponse',
16
- 'RabbitMessageConsumer',
16
+ 'MessageConsumer',
17
17
  'Route',
18
18
  'EndChatResponse',
19
19
  'TransferToHuman',
20
20
  'UserState',
21
+ 'Element',
21
22
  ]
@@ -1,29 +1,32 @@
1
1
  import inspect
2
- from abc import ABC
3
2
  from functools import wraps
4
3
  from logging import debug
5
4
  import json
5
+ from logging import error
6
6
 
7
7
  from ..error.chatbot_error import ChatbotError, ChatbotMessageError
8
- from ..messages.base_message_consumer import MessageConsumer
9
- from ..types.message_types import Message
8
+ from ..messages.message_consumer import MessageConsumer
9
+ from ..types.message_types import UserCall
10
10
  from ..types.output_state import ChatbotResponse, RedirectResponse, EndChatResponse, TransferToHuman
11
11
  from ..types.route import Route
12
12
  from .chatbot_router import ChatbotRouter
13
13
 
14
14
 
15
- class ChatbotApp(ABC):
15
+ class ChatbotApp:
16
16
  """
17
17
  Classe principal para a aplicação do chatbot, gerencia as rotas e a lógica de processamento de mensagens.
18
18
  """
19
19
 
20
- def __init__(self, message_consumer: MessageConsumer):
20
+ def __init__(self, message_consumer: MessageConsumer = None):
21
21
  """
22
22
  Inicializa a classe ChatbotApp com um estado de usuário e um consumidor de mensagens.
23
23
 
24
24
  Args:
25
25
  message_consumer (MessageConsumer): O consumidor de mensagens que lida com a entrada de mensagens no sistema.
26
26
  """
27
+ if not message_consumer:
28
+ message_consumer = MessageConsumer.load_dotenv()
29
+
27
30
  self.__message_consumer = message_consumer
28
31
  self.__routes = {}
29
32
 
@@ -101,12 +104,12 @@ class ChatbotApp(ABC):
101
104
  self.__message_consumer.reprer()
102
105
  self.__message_consumer.start_consume(self.process_message)
103
106
 
104
- def process_message(self, message: Message):
107
+ def process_message(self, userCall: UserCall):
105
108
  """
106
109
  Processa uma mensagem recebida, identificando a rota correspondente e executando a função associada.
107
110
 
108
111
  Args:
109
- message (Message): A mensagem a ser processada.
112
+ userCall (Message): A mensagem a ser processada.
110
113
 
111
114
  Raises:
112
115
  ChatbotMessageError: Se nenhuma rota for encontrada para o menu atual do usuário.
@@ -115,60 +118,51 @@ class ChatbotApp(ABC):
115
118
  Returns:
116
119
  str: A resposta gerada pela função da rota, que pode ser uma mensagem ou o resultado de uma redireção.
117
120
  """
118
- customer_id = message.user_state.customer_id
119
- user_state = message.user_state
120
- menu = message.user_state.menu
121
- menu = menu.lower()
122
- handler = self.__routes.get(menu, None)
121
+ customer_id = userCall.customer_id
122
+ route = userCall.route.lower()
123
+ menu = userCall.menu.lower()
124
+ obs = userCall.obs
125
+ handler = self.__routes.get(route, None)
123
126
 
124
127
  if not handler:
125
128
  raise ChatbotMessageError(
126
- customer_id, f'Rota não encontrada para {menu}!'
129
+ customer_id, f'Rota não encontrada para {route}!'
127
130
  )
131
+
128
132
  func = handler['function']
129
- message_name = handler['params'].get(Message, None)
133
+ userCall_name = handler['params'].get(UserCall, None)
130
134
  route_state_name = handler['params'].get(Route, None)
131
135
 
132
136
  kwargs = dict()
133
- if message_name:
134
- kwargs[message_name] = message
137
+ if userCall_name:
138
+ kwargs[userCall_name] = userCall
135
139
  if route_state_name:
136
- kwargs[route_state_name] = Route(menu, list(self.__routes.keys()))
140
+ kwargs[route_state_name] = Route(route, list(self.__routes.keys()))
137
141
 
138
- message_response = func(**kwargs)
142
+ userCall_response = func(**kwargs)
143
+
144
+ if isinstance(userCall_response, (str, float, int)):
145
+ return self.__create_response(userCall_response, customer_id, route, menu, obs)
146
+
147
+ elif isinstance(userCall_response, ChatbotResponse):
148
+ adjusted_route = self.__adjust_route(userCall_response.route, route)
149
+ return self.__create_response(userCall_response.json(), customer_id, adjusted_route, menu, obs)
150
+
151
+ elif isinstance(userCall_response, (EndChatResponse, TransferToHuman)):
152
+ return self.__end_chat_response(userCall_response, customer_id)
153
+
154
+ elif isinstance(userCall_response, RedirectResponse):
155
+ route = self.__adjust_route(userCall_response.route, route)
156
+ userCall.route = route
157
+ return self.process_message(userCall)
158
+
159
+ elif not userCall_response:
160
+ return self.__create_response('', customer_id, None, None, None)
139
161
 
140
- if type(message_response) in (str, float, int):
141
- response = ChatbotResponse(message_response)
142
- response = message_response.json()
143
- response['user_state'] = {
144
- 'customer_id': customer_id,
145
- 'menu': menu,
146
- 'obs': user_state.obs,
147
- }
148
- return json.dumps(response)
149
- elif type(message_response) == ChatbotResponse:
150
- route = self.__adjust_route(message_response.route, menu)
151
- response = message_response.json()
152
- response['user_state'] = {
153
- 'customer_id': customer_id,
154
- 'menu': route,
155
- 'obs': user_state.obs,
156
- }
157
- return json.dumps(response)
158
- elif type(message_response) in (EndChatResponse, TransferToHuman):
159
- response = message_response.json()
160
- response['user_state'] = {
161
- 'customer_id': customer_id,
162
- 'menu': None,
163
- 'obs': None,
164
- }
165
- return json.dumps(response)
166
- elif type(message_response) == RedirectResponse:
167
- route = self.__adjust_route(message_response.route, menu)
168
- message.user_state.menu = route
169
- return self.process_message(message)
170
162
  else:
171
- raise ChatbotError('Tipo de retorno inválido!')
163
+ error('Tipo de retorno inválido!')
164
+ return None
165
+
172
166
 
173
167
  def __adjust_route(self, route: str, absolute_route: str) -> str:
174
168
  """
@@ -187,4 +181,30 @@ class ChatbotApp(ABC):
187
181
  if 'start' not in route:
188
182
  route = absolute_route + route
189
183
 
190
- return route
184
+ return route
185
+
186
+ def __create_response(self, response, customer_id, route, menu, obs):
187
+ """
188
+ Cria uma resposta padronizada em formato JSON com o estado do usuário.
189
+ """
190
+ response = ChatbotResponse(response).json() if not isinstance(response, dict) else response
191
+ response['user_state'] = {
192
+ 'customer_id': customer_id,
193
+ 'route': route,
194
+ 'menu': menu,
195
+ 'obs': obs,
196
+ }
197
+ return json.dumps(response)
198
+
199
+ def __end_chat_response(self, response, customer_id):
200
+ """
201
+ Gera a resposta de finalização ou transferência de chat.
202
+ """
203
+ response = response.json()
204
+ response['user_state'] = {
205
+ 'customer_id': customer_id,
206
+ 'menu': None,
207
+ 'route': None,
208
+ 'obs': None,
209
+ }
210
+ return json.dumps(response)
@@ -1,3 +1,4 @@
1
+
1
2
  import inspect
2
3
  from functools import wraps
3
4
  from logging import debug
@@ -37,6 +38,7 @@ class ChatbotRouter:
37
38
  signature = inspect.signature(func)
38
39
  output_param = signature.return_annotation
39
40
 
41
+ # Itera sobre os parâmetros da função e extrai seus tipos
40
42
  for name, param in signature.parameters.items():
41
43
  param_type = (
42
44
  param.annotation
@@ -46,6 +48,7 @@ class ChatbotRouter:
46
48
  params[param_type] = name
47
49
  debug(f'Parameter: {name}, Type: {param_type}')
48
50
 
51
+ # Adiciona a função e seus parâmetros à rota especificada
49
52
  self.routes[route_name.strip().lower()] = {
50
53
  'function': func,
51
54
  'params': params,
@@ -74,6 +77,7 @@ class ChatbotRouter:
74
77
  if 'start' not in router.routes.keys():
75
78
  raise ChatbotError('Erro ao incluir rota, start não encontrado!')
76
79
 
80
+ # Adiciona prefixo às rotas do roteador incluído
77
81
  prefixed_routes = {
78
82
  (
79
83
  f'{prefix.lower()}'
@@ -0,0 +1,125 @@
1
+ import os
2
+ import grpc
3
+ import chatgraph.pb.userstate_pb2 as userstate_pb2
4
+ import chatgraph.pb.userstate_pb2_grpc as userstate_pb2_grpc
5
+
6
+ import chatgraph.pb.voll_pb2 as whatsapp_pb2
7
+ import chatgraph.pb.voll_pb2_grpc as whatsapp_pb2_grpc
8
+
9
+ class WhatsappServiceClient:
10
+ def __init__(self, grpc_uri=None):
11
+
12
+ self.grpc_uri = grpc_uri
13
+
14
+ if not grpc_uri:
15
+ self.grpc_uri = os.getenv('GRPC_URI')
16
+
17
+ if not self.grpc_uri:
18
+ raise ValueError("A variável de ambiente 'GRPC_URI' não está definida.")
19
+
20
+ # Cria o canal gRPC
21
+ self.channel = grpc.insecure_channel(self.grpc_uri)
22
+
23
+ # Cria o stub (client) para o serviço gRPC
24
+ self.stub = whatsapp_pb2_grpc.MessageServiceStub(self.channel)
25
+
26
+ def send_button(self, message_data):
27
+ # Cria o request para o método SendButton
28
+ request = whatsapp_pb2.MessageRequest(**message_data)
29
+
30
+ # Faz a chamada ao serviço gRPC
31
+ try:
32
+ response = self.stub.SendButton(request)
33
+ return response
34
+ except grpc.RpcError as e:
35
+ print(f"Erro ao fazer a requisição gRPC SendButton: {e}")
36
+ return None
37
+
38
+ def send_list(self, message_data):
39
+ # Cria o request para o método SendList
40
+ request = whatsapp_pb2.MessageRequest(**message_data)
41
+
42
+ # Faz a chamada ao serviço gRPC
43
+ try:
44
+ response = self.stub.SendList(request)
45
+ return response
46
+ except grpc.RpcError as e:
47
+ print(f"Erro ao fazer a requisição gRPC SendList: {e}")
48
+ return None
49
+
50
+ def send_text(self, message_data):
51
+ # Cria o request para o método SendText
52
+ request = whatsapp_pb2.MessageRequest(**message_data)
53
+
54
+ # Faz a chamada ao serviço gRPC
55
+ try:
56
+ response = self.stub.SendText(request)
57
+ return response
58
+ except grpc.RpcError as e:
59
+ print(f"Erro ao fazer a requisição gRPC SendText: {e}")
60
+ return None
61
+
62
+ class UserStateServiceClient:
63
+ def __init__(self, grpc_uri=None):
64
+
65
+ self.grpc_uri = grpc_uri
66
+
67
+ if not grpc_uri:
68
+ self.grpc_uri = os.getenv('GRPC_URI')
69
+
70
+ if not self.grpc_uri:
71
+ raise ValueError("A variável de ambiente 'GRPC_URI' não está definida.")
72
+
73
+ # Cria o canal gRPC
74
+ self.channel = grpc.insecure_channel(self.grpc_uri)
75
+
76
+ # Cria o stub (client) para o serviço gRPC
77
+ self.stub = userstate_pb2_grpc.UserStateServiceStub(self.channel)
78
+
79
+ def select_user_state(self, user_id):
80
+ # Cria o request para o método SelectUserState
81
+ request = userstate_pb2.UserStateId(user_id=user_id)
82
+
83
+ # Faz a chamada ao serviço gRPC
84
+ try:
85
+ response = self.stub.SelectUserState(request)
86
+ return response
87
+ except grpc.RpcError as e:
88
+ print(f"Erro ao fazer a requisição gRPC SelectUserState: {e}")
89
+ return None
90
+
91
+ def insert_user_state(self, user_state_data):
92
+ # Cria o request para o método InsertUserState
93
+ request = userstate_pb2.UserState(**user_state_data)
94
+
95
+ # Faz a chamada ao serviço gRPC
96
+ try:
97
+ response = self.stub.InsertUserState(request)
98
+ return response
99
+ except grpc.RpcError as e:
100
+ print(f"Erro ao fazer a requisição gRPC InsertUserState: {e}")
101
+ return None
102
+
103
+ def update_user_state(self, user_state_data):
104
+ # Cria o request para o método UpdateUserState
105
+ request = userstate_pb2.UserState(**user_state_data)
106
+
107
+ # Faz a chamada ao serviço gRPC
108
+ try:
109
+ response = self.stub.UpdateUserState(request)
110
+ return response
111
+ except grpc.RpcError as e:
112
+ print(f"Erro ao fazer a requisição gRPC UpdateUserState: {e}")
113
+ return None
114
+
115
+ def delete_user_state(self, user_id):
116
+ # Cria o request para o método DeleteUserState
117
+ request = userstate_pb2.UserStateId(user_id=user_id)
118
+
119
+ # Faz a chamada ao serviço gRPC
120
+ try:
121
+ response = self.stub.DeleteUserState(request)
122
+ return response
123
+ except grpc.RpcError as e:
124
+ print(f"Erro ao fazer a requisição gRPC DeleteUserState: {e}")
125
+ return None
@@ -4,14 +4,13 @@ import os
4
4
  import pika
5
5
  from typing import Callable
6
6
  from ..auth.credentials import Credential
7
- from ..types.message_types import Message, UserState
8
- from .base_message_consumer import MessageConsumer
7
+ from ..types.message_types import UserCall, UserState
9
8
  from rich.console import Console
10
9
  from rich.table import Table
11
10
  from rich.text import Text
12
11
  from rich.panel import Panel
13
12
 
14
- class RabbitMessageConsumer(MessageConsumer):
13
+ class MessageConsumer:
15
14
  """
16
15
  Implementação de MessageConsumer para consumir mensagens de uma fila RabbitMQ.
17
16
 
@@ -27,6 +26,7 @@ class RabbitMessageConsumer(MessageConsumer):
27
26
  self,
28
27
  credential: Credential,
29
28
  amqp_url: str,
29
+ grpc_uri: str,
30
30
  queue_consume: str,
31
31
  prefetch_count: int = 1,
32
32
  virtual_host: str = '/',
@@ -45,6 +45,7 @@ class RabbitMessageConsumer(MessageConsumer):
45
45
  self.__prefetch_count = prefetch_count
46
46
  self.__queue_consume = queue_consume
47
47
  self.__amqp_url = amqp_url
48
+ self.__grpc_uri = grpc_uri
48
49
  self.__credentials = pika.PlainCredentials(
49
50
  credential.username, credential.password
50
51
  )
@@ -58,7 +59,8 @@ class RabbitMessageConsumer(MessageConsumer):
58
59
  queue_env: str = 'RABBIT_QUEUE',
59
60
  prefetch_env: str = 'RABBIT_PREFETCH',
60
61
  vhost_env: str = 'RABBIT_VHOST',
61
- ) -> 'RabbitMessageConsumer':
62
+ grpc_uri: str = 'GRPC_URI',
63
+ ) -> 'MessageConsumer':
62
64
  """
63
65
  Carrega as configurações do RabbitMQ a partir de variáveis de ambiente e retorna uma instância de RabbitMessageConsumer.
64
66
 
@@ -82,8 +84,9 @@ class RabbitMessageConsumer(MessageConsumer):
82
84
  queue = os.getenv(queue_env)
83
85
  prefetch = os.getenv(prefetch_env, 1)
84
86
  vhost = os.getenv(vhost_env, '/')
87
+ grpc = os.getenv(grpc_uri)
85
88
 
86
- if not username or not password or not url or not queue:
89
+ if not username or not password or not url or not queue or not grpc:
87
90
  raise ValueError('Corrija as variáveis de ambiente!')
88
91
 
89
92
  return cls(
@@ -92,6 +95,7 @@ class RabbitMessageConsumer(MessageConsumer):
92
95
  queue_consume=queue,
93
96
  prefetch_count=int(prefetch),
94
97
  virtual_host=vhost,
98
+ grpc_uri=grpc,
95
99
  )
96
100
 
97
101
  def start_consume(self, process_message: Callable) -> None:
@@ -122,7 +126,7 @@ class RabbitMessageConsumer(MessageConsumer):
122
126
  ),
123
127
  )
124
128
 
125
- info('[x] Aguardando solicitações RPC')
129
+ info('[x] Server inicializado! Aguardando solicitações RPC')
126
130
  channel.start_consuming()
127
131
  except pika.exceptions.StreamLostError as e:
128
132
  debug(e)
@@ -154,7 +158,7 @@ class RabbitMessageConsumer(MessageConsumer):
154
158
  )
155
159
  ch.basic_ack(delivery_tag=method.delivery_tag)
156
160
 
157
- def __transform_message(self, message: dict) -> Message:
161
+ def __transform_message(self, message: dict) -> UserCall:
158
162
  """
159
163
  Transforma o dicionário JSON recebido em uma instância de Message.
160
164
 
@@ -166,12 +170,13 @@ class RabbitMessageConsumer(MessageConsumer):
166
170
  """
167
171
 
168
172
  user_state = message.get('user_state', {})
169
- return Message(
173
+ return UserCall(
170
174
  type=message.get('type', ''),
171
175
  text=message.get('text', ''),
172
176
  user_state=UserState(
173
177
  customer_id=user_state.get('customer_id', ''),
174
178
  menu=user_state.get('menu', ''),
179
+ route=user_state.get('route', ''),
175
180
  lst_update=user_state.get('lst_update', ''),
176
181
  obs=user_state.get('obs', {}),
177
182
  ),
@@ -179,6 +184,7 @@ class RabbitMessageConsumer(MessageConsumer):
179
184
  customer_phone=message.get('customer_phone', ''),
180
185
  company_phone=message.get('company_phone', ''),
181
186
  status=message.get('status'),
187
+ grpc_uri=self.__grpc_uri,
182
188
  )
183
189
 
184
190
  def reprer(self):
@@ -0,0 +1,29 @@
1
+ syntax = "proto3";
2
+
3
+ package userstate;
4
+
5
+ option go_package = "./pb/userstate";
6
+
7
+ service UserStateService {
8
+ rpc SelectUserState(UserStateId) returns (UserState);
9
+ rpc InsertUserState(UserState) returns (RequestStatus);
10
+ rpc UpdateUserState(UserState) returns (RequestStatus);
11
+ rpc DeleteUserState(UserStateId) returns (RequestStatus);
12
+ }
13
+
14
+ message UserState {
15
+ string user_id = 1;
16
+ string menu_id = 2;
17
+ string route = 3;
18
+ string obs = 4;
19
+ string date = 5;
20
+ }
21
+
22
+ message UserStateId {
23
+ string user_id = 1;
24
+ }
25
+
26
+ message RequestStatus {
27
+ bool status = 1;
28
+ string message = 2;
29
+ }
@@ -0,0 +1,43 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: userstate.proto
5
+ # Protobuf Python Version: 5.27.2
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 5,
15
+ 27,
16
+ 2,
17
+ '',
18
+ 'userstate.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+
26
+
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fuserstate.proto\x12\tuserstate\"W\n\tUserState\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x0f\n\x07menu_id\x18\x02 \x01(\t\x12\r\n\x05route\x18\x03 \x01(\t\x12\x0b\n\x03obs\x18\x04 \x01(\t\x12\x0c\n\x04\x64\x61te\x18\x05 \x01(\t\"\x1e\n\x0bUserStateId\x12\x0f\n\x07user_id\x18\x01 \x01(\t\"0\n\rRequestStatus\x12\x0e\n\x06status\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t2\x9e\x02\n\x10UserStateService\x12?\n\x0fSelectUserState\x12\x16.userstate.UserStateId\x1a\x14.userstate.UserState\x12\x41\n\x0fInsertUserState\x12\x14.userstate.UserState\x1a\x18.userstate.RequestStatus\x12\x41\n\x0fUpdateUserState\x12\x14.userstate.UserState\x1a\x18.userstate.RequestStatus\x12\x43\n\x0f\x44\x65leteUserState\x12\x16.userstate.UserStateId\x1a\x18.userstate.RequestStatusB\x10Z\x0e./pb/userstateb\x06proto3')
28
+
29
+ _globals = globals()
30
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'userstate_pb2', _globals)
32
+ if not _descriptor._USE_C_DESCRIPTORS:
33
+ _globals['DESCRIPTOR']._loaded_options = None
34
+ _globals['DESCRIPTOR']._serialized_options = b'Z\016./pb/userstate'
35
+ _globals['_USERSTATE']._serialized_start=30
36
+ _globals['_USERSTATE']._serialized_end=117
37
+ _globals['_USERSTATEID']._serialized_start=119
38
+ _globals['_USERSTATEID']._serialized_end=149
39
+ _globals['_REQUESTSTATUS']._serialized_start=151
40
+ _globals['_REQUESTSTATUS']._serialized_end=199
41
+ _globals['_USERSTATESERVICE']._serialized_start=202
42
+ _globals['_USERSTATESERVICE']._serialized_end=488
43
+ # @@protoc_insertion_point(module_scope)