chatgraph 0.6.4__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.
- chatgraph/__init__.py +35 -0
- chatgraph/auth/credentials.py +71 -0
- chatgraph/bot/chatbot_model.py +238 -0
- chatgraph/bot/chatbot_router.py +85 -0
- chatgraph/bot/default_functions.py +24 -0
- chatgraph/cli/__init__.py +187 -0
- chatgraph/error/chatbot_error.py +57 -0
- chatgraph/error/route_error.py +26 -0
- chatgraph/gRPC/gRPCCall.py +189 -0
- chatgraph/messages/message_consumer.py +212 -0
- chatgraph/models/actions.py +138 -0
- chatgraph/models/message.py +350 -0
- chatgraph/models/userstate.py +214 -0
- chatgraph/pb/router.proto +151 -0
- chatgraph/pb/router_pb2.py +79 -0
- chatgraph/pb/router_pb2_grpc.py +902 -0
- chatgraph/services/__init__.py +0 -0
- chatgraph/services/router_http_client.py +451 -0
- chatgraph/types/background_task.py +27 -0
- chatgraph/types/end_types.py +75 -0
- chatgraph/types/route.py +104 -0
- chatgraph/types/usercall.py +229 -0
- chatgraph-0.6.4.dist-info/METADATA +521 -0
- chatgraph-0.6.4.dist-info/RECORD +27 -0
- chatgraph-0.6.4.dist-info/WHEEL +4 -0
- chatgraph-0.6.4.dist-info/entry_points.txt +3 -0
- chatgraph-0.6.4.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class RouteError(Exception):
|
|
2
|
+
"""
|
|
3
|
+
Exceção personalizada para erros relacionados a rotas no sistema do chatbot.
|
|
4
|
+
|
|
5
|
+
Atributos:
|
|
6
|
+
message (str): A mensagem de erro descrevendo o problema.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str):
|
|
10
|
+
"""
|
|
11
|
+
Inicializa a exceção RouteError com uma mensagem de erro.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
message (str): A mensagem de erro descrevendo o problema.
|
|
15
|
+
"""
|
|
16
|
+
self.message = message
|
|
17
|
+
super().__init__(self.message)
|
|
18
|
+
|
|
19
|
+
def __str__(self):
|
|
20
|
+
"""
|
|
21
|
+
Retorna a representação em string da exceção RouteError.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
str: Uma string formatada que inclui o nome da exceção e a mensagem de erro.
|
|
25
|
+
"""
|
|
26
|
+
return f'RouteError: {self.message}'
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import grpc
|
|
3
|
+
import json
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
import chatgraph.pb.router_pb2 as chatbot_pb2
|
|
7
|
+
import chatgraph.pb.router_pb2_grpc as chatbot_pb2_grpc
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RouterServiceClient:
|
|
11
|
+
def __init__(self, grpc_uri=None):
|
|
12
|
+
self.grpc_uri = grpc_uri or os.getenv("GRPC_URI")
|
|
13
|
+
|
|
14
|
+
if not self.grpc_uri:
|
|
15
|
+
raise ValueError("A variável de ambiente 'GRPC_URI' não está definida.")
|
|
16
|
+
|
|
17
|
+
# Cria o canal gRPC
|
|
18
|
+
self.channel = grpc.insecure_channel(self.grpc_uri)
|
|
19
|
+
|
|
20
|
+
# Cria os stubs para os serviços gRPC
|
|
21
|
+
self.user_state_stub = chatbot_pb2_grpc.UserStateServiceStub(self.channel)
|
|
22
|
+
self.send_message_stub = chatbot_pb2_grpc.SendMessageStub(self.channel)
|
|
23
|
+
self.transfer_stub = chatbot_pb2_grpc.TransferStub(self.channel)
|
|
24
|
+
self.end_chat_stub = chatbot_pb2_grpc.EndChatStub(self.channel)
|
|
25
|
+
|
|
26
|
+
self.console = Console()
|
|
27
|
+
|
|
28
|
+
def insert_update_user_state(self, user_state_data):
|
|
29
|
+
request = chatbot_pb2.UserState(**user_state_data)
|
|
30
|
+
try:
|
|
31
|
+
response = self.user_state_stub.InsertUpdateUserState(request)
|
|
32
|
+
if not response.status:
|
|
33
|
+
self.console.print(
|
|
34
|
+
f"Erro ao chamar InsertUpdateUserState: {response.message}",
|
|
35
|
+
style="bold red",
|
|
36
|
+
)
|
|
37
|
+
return response
|
|
38
|
+
except grpc.RpcError as e:
|
|
39
|
+
self.console.print(
|
|
40
|
+
f"Erro ao chamar InsertUpdateUserState: {e}", style="bold red"
|
|
41
|
+
)
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
def delete_user_state(self, chat_id_data):
|
|
45
|
+
request = chatbot_pb2.ChatID(**chat_id_data)
|
|
46
|
+
try:
|
|
47
|
+
response = self.user_state_stub.DeleteUserState(request)
|
|
48
|
+
if not response.status:
|
|
49
|
+
self.console.print(
|
|
50
|
+
f"Erro ao chamar SendMessage: {response.message}", style="bold red"
|
|
51
|
+
)
|
|
52
|
+
return response
|
|
53
|
+
except grpc.RpcError as e:
|
|
54
|
+
self.console.print(f"Erro ao chamar DeleteUserState: {e}", style="bold red")
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def get_user_state(self, chat_id_data):
|
|
58
|
+
request = chatbot_pb2.ChatID(**chat_id_data)
|
|
59
|
+
try:
|
|
60
|
+
response = self.user_state_stub.GetUserState(request)
|
|
61
|
+
return response
|
|
62
|
+
except grpc.RpcError as e:
|
|
63
|
+
self.console.print(f"Erro ao chamar GetUserState: {e}", style="bold red")
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def send_message(self, message_data):
|
|
67
|
+
# print(json.dumps(message_data))
|
|
68
|
+
|
|
69
|
+
request = chatbot_pb2.Message(**message_data)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
response = self.send_message_stub.SendMessage(request)
|
|
73
|
+
if not response.status:
|
|
74
|
+
self.console.print(
|
|
75
|
+
f"Erro ao chamar SendMessage: {response.message}", style="bold red"
|
|
76
|
+
)
|
|
77
|
+
return response
|
|
78
|
+
except grpc.RpcError as e:
|
|
79
|
+
self.console.print(f"Erro ao chamar SendMessage: {e}", style="bold red")
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def send_image(self, message_data):
|
|
83
|
+
# print(json.dumps(message_data))
|
|
84
|
+
|
|
85
|
+
request = chatbot_pb2.FileMessage(**message_data)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
response = self.send_message_stub.SendImage(request)
|
|
89
|
+
if not response.status and response.message != "arquivo não encontrado":
|
|
90
|
+
self.console.print(
|
|
91
|
+
f"Erro ao chamar SendImage: {response.message}", style="bold red"
|
|
92
|
+
)
|
|
93
|
+
elif response.message == "arquivo não encontrado":
|
|
94
|
+
print("Arquivo não encontrado, Carregando arquivo...")
|
|
95
|
+
return response
|
|
96
|
+
except grpc.RpcError as e:
|
|
97
|
+
self.console.print(f"Erro ao chamar SendImage: {e}", style="bold red")
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
def upload_file(self, file_data):
|
|
101
|
+
request = chatbot_pb2.UploadFileRequest(**file_data)
|
|
102
|
+
try:
|
|
103
|
+
response = self.send_message_stub.UploadFile(request)
|
|
104
|
+
if not response.status:
|
|
105
|
+
self.console.print(
|
|
106
|
+
f"Erro ao chamar UploadFile: {response.message}", style="bold red"
|
|
107
|
+
)
|
|
108
|
+
return response
|
|
109
|
+
except grpc.RpcError as e:
|
|
110
|
+
self.console.print(f"Erro ao chamar UploadFile: {e}", style="bold red")
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
def transfer_to_human(self, transfer_request_data):
|
|
114
|
+
request = chatbot_pb2.TransferToHumanRequest(**transfer_request_data)
|
|
115
|
+
try:
|
|
116
|
+
response = self.transfer_stub.TransferToHuman(request)
|
|
117
|
+
if not response.status:
|
|
118
|
+
self.console.print(
|
|
119
|
+
f"Erro ao chamar SendMessage: {response.message}", style="bold red"
|
|
120
|
+
)
|
|
121
|
+
return response
|
|
122
|
+
except grpc.RpcError as e:
|
|
123
|
+
self.console.print(f"Erro ao chamar TransferToHuman: {e}", style="bold red")
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
def transfer_to_menu(self, transfer_request_data):
|
|
127
|
+
request = chatbot_pb2.TransferToMenuRequest(**transfer_request_data)
|
|
128
|
+
try:
|
|
129
|
+
response = self.transfer_stub.TransferToMenu(request)
|
|
130
|
+
if not response.status:
|
|
131
|
+
self.console.print(
|
|
132
|
+
f"Erro ao chamar TransferToMenu: {response.message}",
|
|
133
|
+
style="bold red",
|
|
134
|
+
)
|
|
135
|
+
return response
|
|
136
|
+
except grpc.RpcError as e:
|
|
137
|
+
self.console.print(f"Erro ao chamar TransferToMenu: {e}", style="bold red")
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
def end_chat(self, end_chat_request_data):
|
|
141
|
+
request = chatbot_pb2.EndChatRequest(**end_chat_request_data)
|
|
142
|
+
try:
|
|
143
|
+
response = self.end_chat_stub.EndChat(request)
|
|
144
|
+
if not response.status:
|
|
145
|
+
self.console.print(
|
|
146
|
+
f"Erro ao chamar SendMessage: {response.message}", style="bold red"
|
|
147
|
+
)
|
|
148
|
+
return response
|
|
149
|
+
except grpc.RpcError as e:
|
|
150
|
+
self.console.print(f"Erro ao chamar EndChat: {e}", style="bold red")
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
def get_campaign_id(self, campaign_name):
|
|
154
|
+
request = chatbot_pb2.CampaignName(**campaign_name)
|
|
155
|
+
try:
|
|
156
|
+
response = self.transfer_stub.GetCampaignID(request)
|
|
157
|
+
return response
|
|
158
|
+
except grpc.RpcError as e:
|
|
159
|
+
self.console.print(f"Erro ao chamar GetCampaignID: {e}", style="bold red")
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
def get_all_campaigns(self):
|
|
163
|
+
request = chatbot_pb2.Void()
|
|
164
|
+
try:
|
|
165
|
+
response = self.transfer_stub.GetAllCampaigns(request)
|
|
166
|
+
return response
|
|
167
|
+
except grpc.RpcError as e:
|
|
168
|
+
self.console.print(f"Erro ao chamar GetAllCampaigns: {e}", style="bold red")
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
def get_tabulation_id(self, tabulation_name):
|
|
172
|
+
request = chatbot_pb2.TabulationName(**tabulation_name)
|
|
173
|
+
try:
|
|
174
|
+
response = self.end_chat_stub.GetTabulationID(request)
|
|
175
|
+
return response
|
|
176
|
+
except grpc.RpcError as e:
|
|
177
|
+
self.console.print(f"Erro ao chamar GetTabulationID: {e}", style="bold red")
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
def get_all_tabulations(self):
|
|
181
|
+
request = chatbot_pb2.Void()
|
|
182
|
+
try:
|
|
183
|
+
response = self.end_chat_stub.GetAllTabulations(request)
|
|
184
|
+
return response
|
|
185
|
+
except grpc.RpcError as e:
|
|
186
|
+
self.console.print(
|
|
187
|
+
f"Erro ao chamar GetAllTabulations: {e}", style="bold red"
|
|
188
|
+
)
|
|
189
|
+
return None
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from logging import info
|
|
3
|
+
import os
|
|
4
|
+
import aio_pika
|
|
5
|
+
from typing import Callable
|
|
6
|
+
from ..auth.credentials import Credential
|
|
7
|
+
from ..models.message import Message
|
|
8
|
+
from ..models.userstate import UserState
|
|
9
|
+
from ..services.router_http_client import RouterHTTPClient
|
|
10
|
+
from ..types.usercall import UserCall
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
from rich.text import Text
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from urllib.parse import quote
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MessageConsumer:
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
credential: Credential,
|
|
22
|
+
amqp_url: str,
|
|
23
|
+
router_url: str,
|
|
24
|
+
router_token: str,
|
|
25
|
+
queue_consume: str,
|
|
26
|
+
prefetch_count: int = 1,
|
|
27
|
+
virtual_host: str = '/',
|
|
28
|
+
) -> None:
|
|
29
|
+
self.__virtual_host = virtual_host
|
|
30
|
+
self.__prefetch_count = prefetch_count
|
|
31
|
+
self.__queue_consume = queue_consume
|
|
32
|
+
self.__amqp_url = amqp_url
|
|
33
|
+
self.__router_url = router_url
|
|
34
|
+
self.__router_token = router_token
|
|
35
|
+
self.__credentials = credential
|
|
36
|
+
self.__router_client = None
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def load_dotenv(
|
|
40
|
+
cls,
|
|
41
|
+
user_env: str = 'RABBIT_USER',
|
|
42
|
+
pass_env: str = 'RABBIT_PASS',
|
|
43
|
+
uri_env: str = 'RABBIT_URI',
|
|
44
|
+
queue_env: str = 'RABBIT_QUEUE',
|
|
45
|
+
prefetch_env: str = 'RABBIT_PREFETCH',
|
|
46
|
+
vhost_env: str = 'RABBIT_VHOST',
|
|
47
|
+
router_env: str = 'ROUTER_URL',
|
|
48
|
+
router_token_env: str = 'ROUTER_TOKEN',
|
|
49
|
+
) -> 'MessageConsumer':
|
|
50
|
+
username = os.getenv(user_env)
|
|
51
|
+
password = os.getenv(pass_env)
|
|
52
|
+
url = os.getenv(uri_env)
|
|
53
|
+
queue = os.getenv(queue_env)
|
|
54
|
+
prefetch = os.getenv(prefetch_env, '1')
|
|
55
|
+
vhost = os.getenv(vhost_env, '/')
|
|
56
|
+
router_url = os.getenv(router_env)
|
|
57
|
+
router_token = os.getenv(router_token_env)
|
|
58
|
+
|
|
59
|
+
envs_essentials = {
|
|
60
|
+
username: user_env,
|
|
61
|
+
password: pass_env,
|
|
62
|
+
url: uri_env,
|
|
63
|
+
queue: queue_env,
|
|
64
|
+
router_url: router_env,
|
|
65
|
+
router_token: router_token_env,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if None in envs_essentials:
|
|
69
|
+
envs_missing = [v for k, v in envs_essentials.items() if k is None]
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f'Corrija as variáveis de ambiente: {envs_missing}'
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return cls(
|
|
75
|
+
credential=Credential(username=username, password=password),
|
|
76
|
+
amqp_url=url,
|
|
77
|
+
queue_consume=queue,
|
|
78
|
+
prefetch_count=int(prefetch),
|
|
79
|
+
virtual_host=vhost,
|
|
80
|
+
router_url=router_url,
|
|
81
|
+
router_token=router_token,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
async def __initialize_router(self) -> RouterHTTPClient:
|
|
85
|
+
"""Inicializa o cliente HTTP apenas uma vez (singleton)."""
|
|
86
|
+
if self.__router_client is None:
|
|
87
|
+
self.__router_client = RouterHTTPClient(
|
|
88
|
+
base_url=self.__router_url,
|
|
89
|
+
username='chatgraph',
|
|
90
|
+
password=self.__router_token,
|
|
91
|
+
)
|
|
92
|
+
return self.__router_client
|
|
93
|
+
|
|
94
|
+
async def start_consume(self, process_message: Callable):
|
|
95
|
+
try:
|
|
96
|
+
# Inicializar cliente HTTP uma única vez
|
|
97
|
+
await self.__initialize_router()
|
|
98
|
+
|
|
99
|
+
user = quote(self.__credentials.username)
|
|
100
|
+
pwd = quote(self.__credentials.password)
|
|
101
|
+
vhost = quote(self.__virtual_host)
|
|
102
|
+
uri = self.__amqp_url
|
|
103
|
+
amqp_url = f'amqp://{user}:{pwd}@{uri}/{vhost}'
|
|
104
|
+
connection = await aio_pika.connect_robust(amqp_url)
|
|
105
|
+
|
|
106
|
+
async with connection:
|
|
107
|
+
channel = await connection.channel()
|
|
108
|
+
await channel.set_qos(prefetch_count=self.__prefetch_count)
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
queue = await channel.get_queue(
|
|
112
|
+
self.__queue_consume, ensure=True
|
|
113
|
+
)
|
|
114
|
+
except aio_pika.exceptions.ChannelNotFoundEntity:
|
|
115
|
+
arguments = {
|
|
116
|
+
'x-dead-letter-exchange': 'log_error', # Dead Letter Exchange
|
|
117
|
+
'x-expires': 86400000, # Expiração da fila (em milissegundos)
|
|
118
|
+
'x-message-ttl': 300000, # Tempo de vida das mensagens (em milissegundos)
|
|
119
|
+
}
|
|
120
|
+
queue = await channel.declare_queue(
|
|
121
|
+
self.__queue_consume,
|
|
122
|
+
durable=True,
|
|
123
|
+
arguments=arguments,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
info('[x] Server inicializado! Aguardando solicitações RPC')
|
|
127
|
+
|
|
128
|
+
async for message in queue:
|
|
129
|
+
async with message.process():
|
|
130
|
+
await self.on_request(message.body, process_message)
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
print(f'Erro durante o consumo de mensagens: {e}')
|
|
134
|
+
# Reiniciar a conexão em caso de falha
|
|
135
|
+
# await self.start_consume(process_message)
|
|
136
|
+
finally:
|
|
137
|
+
# Fechar cliente HTTP quando o consumer parar
|
|
138
|
+
await self.cleanup()
|
|
139
|
+
|
|
140
|
+
async def on_request(self, body: bytes, process_message: Callable):
|
|
141
|
+
try:
|
|
142
|
+
message = body.decode()
|
|
143
|
+
message_json = json.loads(message)
|
|
144
|
+
pure_message = await self.__transform_message(message_json)
|
|
145
|
+
await process_message(pure_message)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
print(f'Erro ao processar mensagem: {e}')
|
|
148
|
+
|
|
149
|
+
async def __transform_message(self, message: dict) -> UserCall:
|
|
150
|
+
user_state = message.get('user_state', {})
|
|
151
|
+
message_data = message.get('message', {})
|
|
152
|
+
observation = user_state.get('observation', {})
|
|
153
|
+
|
|
154
|
+
if isinstance(observation, str):
|
|
155
|
+
observation = json.loads(observation)
|
|
156
|
+
|
|
157
|
+
user_state_models = UserState.from_dict(user_state)
|
|
158
|
+
message_models = Message.from_dict(message_data)
|
|
159
|
+
|
|
160
|
+
# Reutilizar o mesmo cliente para todas as mensagens
|
|
161
|
+
router_client = await self.__initialize_router()
|
|
162
|
+
|
|
163
|
+
usercall = UserCall(
|
|
164
|
+
user_state=user_state_models,
|
|
165
|
+
message=message_models,
|
|
166
|
+
router_client=router_client,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return usercall
|
|
170
|
+
|
|
171
|
+
async def cleanup(self):
|
|
172
|
+
"""Libera recursos do cliente HTTP."""
|
|
173
|
+
if self.__router_client:
|
|
174
|
+
await self.__router_client.close()
|
|
175
|
+
self.__router_client = None
|
|
176
|
+
print('✓ RouterHTTPClient fechado')
|
|
177
|
+
|
|
178
|
+
def reprer(self):
|
|
179
|
+
console = Console()
|
|
180
|
+
|
|
181
|
+
title_text = Text('ChatGraph', style='bold red', justify='center')
|
|
182
|
+
title_panel = Panel.fit(
|
|
183
|
+
title_text, title=' ', border_style='bold red', padding=(1, 4)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
separator = Text(
|
|
187
|
+
'🐇🐇🐇 RabbitMessageConsumer 📨📨📨',
|
|
188
|
+
style='cyan',
|
|
189
|
+
justify='center',
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
table = Table(
|
|
193
|
+
show_header=True,
|
|
194
|
+
header_style='bold magenta',
|
|
195
|
+
title='RabbitMQ Consumer',
|
|
196
|
+
)
|
|
197
|
+
table.add_column(
|
|
198
|
+
'Atributo', justify='center', style='cyan', no_wrap=True
|
|
199
|
+
)
|
|
200
|
+
table.add_column('Valor', justify='center', style='magenta')
|
|
201
|
+
|
|
202
|
+
table.add_row('Virtual Host', self.__virtual_host)
|
|
203
|
+
table.add_row('Prefetch Count', str(self.__prefetch_count))
|
|
204
|
+
table.add_row('Queue Consume', self.__queue_consume)
|
|
205
|
+
table.add_row('AMQP URL', self.__amqp_url)
|
|
206
|
+
table.add_row('Rabbit Username', self.__credentials.username)
|
|
207
|
+
table.add_row('Rabbit Password', '******')
|
|
208
|
+
table.add_row('Router URL', self.__router_url)
|
|
209
|
+
|
|
210
|
+
console.print(title_panel, justify='center')
|
|
211
|
+
console.print(separator, justify='center')
|
|
212
|
+
console.print(table, justify='center')
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modelos de dados para ações do chatbot.
|
|
3
|
+
|
|
4
|
+
Este módulo contém as dataclasses e enums para representar ações
|
|
5
|
+
como transferências e encerramentos de chat.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ActionType(Enum):
|
|
13
|
+
"""
|
|
14
|
+
Tipo de ação no sistema.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
TRANSFER: Transferência para atendimento humano
|
|
18
|
+
END_CHAT: Encerramento de chat
|
|
19
|
+
MESSAGE: Mensagem
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
TRANSFER = "TRANSFER"
|
|
23
|
+
END_CHAT = "END_CHAT"
|
|
24
|
+
MESSAGE = "MESSAGE"
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_string(cls, value: str) -> "ActionType":
|
|
28
|
+
"""
|
|
29
|
+
Cria ActionType a partir de string.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
value: String representando o tipo
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
ActionType correspondente
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
ValueError: Se o valor não for válido
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
return cls(value.upper())
|
|
42
|
+
except ValueError:
|
|
43
|
+
raise ValueError(f"invalid action type: {value}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class EndAction:
|
|
48
|
+
"""
|
|
49
|
+
Ação de encerramento de chat.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
id: ID único da ação
|
|
53
|
+
name: Nome da ação/tabulação
|
|
54
|
+
department_id: ID do departamento
|
|
55
|
+
observation: Observação sobre o encerramento
|
|
56
|
+
last_update: Data/hora de última atualização (ISO 8601)
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
id: str = ""
|
|
60
|
+
name: str = ""
|
|
61
|
+
department_id: int = 0
|
|
62
|
+
observation: str = ""
|
|
63
|
+
last_update: str = ""
|
|
64
|
+
|
|
65
|
+
def is_empty(self) -> bool:
|
|
66
|
+
"""Verifica se a ação está vazia."""
|
|
67
|
+
return not self.id and not self.name
|
|
68
|
+
|
|
69
|
+
def to_dict(self) -> dict:
|
|
70
|
+
"""Converte para dicionário."""
|
|
71
|
+
return {
|
|
72
|
+
"id": self.id,
|
|
73
|
+
"name": self.name,
|
|
74
|
+
"department_id": self.department_id,
|
|
75
|
+
"observation": self.observation,
|
|
76
|
+
"last_update": self.last_update,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_dict(cls, data: dict) -> "EndAction":
|
|
81
|
+
"""Cria instância a partir de dicionário."""
|
|
82
|
+
return cls(
|
|
83
|
+
id=data.get("id", ""),
|
|
84
|
+
name=data.get("name", ""),
|
|
85
|
+
department_id=data.get("department_id", 0),
|
|
86
|
+
observation=data.get("observation", ""),
|
|
87
|
+
last_update=data.get("last_update", ""),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class TransferToHumanAction:
|
|
93
|
+
"""
|
|
94
|
+
Ação de transferência para atendimento humano.
|
|
95
|
+
|
|
96
|
+
Attributes:
|
|
97
|
+
id: ID único da ação
|
|
98
|
+
name: Nome da campanha/fila
|
|
99
|
+
department_id: ID do departamento
|
|
100
|
+
observation: Observação sobre a transferência
|
|
101
|
+
last_update: Data/hora de última atualização (ISO 8601)
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
id: int = 0
|
|
105
|
+
name: str = ""
|
|
106
|
+
department_id: int = 0
|
|
107
|
+
observation: str = ""
|
|
108
|
+
last_update: str = ""
|
|
109
|
+
|
|
110
|
+
def is_empty(self) -> bool:
|
|
111
|
+
"""Verifica se a ação está vazia."""
|
|
112
|
+
return not self.id and not self.name
|
|
113
|
+
|
|
114
|
+
def to_dict(self) -> dict:
|
|
115
|
+
"""Converte para dicionário."""
|
|
116
|
+
return {
|
|
117
|
+
"id": self.id,
|
|
118
|
+
"name": self.name,
|
|
119
|
+
"department_id": self.department_id,
|
|
120
|
+
"observation": self.observation,
|
|
121
|
+
"last_update": self.last_update,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def from_dict(cls, data: dict) -> "TransferToHumanAction":
|
|
126
|
+
"""Cria instância a partir de dicionário."""
|
|
127
|
+
return cls(
|
|
128
|
+
id=data.get("id", 0),
|
|
129
|
+
name=data.get("name", ""),
|
|
130
|
+
department_id=data.get("department_id", 0),
|
|
131
|
+
observation=data.get("observation", ""),
|
|
132
|
+
last_update=data.get("last_update", ""),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Instâncias vazias para comparação
|
|
137
|
+
EMPTY_END_ACTION = EndAction()
|
|
138
|
+
EMPTY_TRANSFER_TO_HUMAN_ACTION = TransferToHumanAction()
|