letschatty 0.4.344__py3-none-any.whl → 0.4.345__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.
- letschatty/models/ai_microservices/lambda_events.py +4 -17
- letschatty/models/analytics/events/__init__.py +1 -0
- letschatty/models/analytics/events/chat_based_events/chat_client.py +19 -0
- letschatty/models/analytics/events/event_type_to_classes.py +1 -0
- letschatty/models/analytics/events/event_types.py +2 -0
- letschatty/models/chat/chat.py +2 -11
- letschatty/models/company/__init__.py +1 -6
- letschatty/models/company/assets/ai_agents_v2/chain_of_thought_in_chat.py +3 -5
- letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_in_chat.py +34 -2
- letschatty/models/company/assets/chat_assets.py +2 -12
- letschatty/models/company/assets/sale.py +3 -3
- letschatty/models/company/empresa.py +1 -2
- letschatty/models/company/form_field.py +2 -9
- letschatty/services/chat/chat_service.py +13 -75
- letschatty/services/factories/analytics/events_factory.py +24 -0
- letschatty/services/validators/analytics_validator.py +0 -11
- {letschatty-0.4.344.dist-info → letschatty-0.4.345.dist-info}/METADATA +1 -1
- {letschatty-0.4.344.dist-info → letschatty-0.4.345.dist-info}/RECORD +20 -25
- letschatty/models/company/assets/ai_agents_v2/statuses.py +0 -33
- letschatty/models/company/integrations/product_sync_status.py +0 -28
- letschatty/models/company/integrations/shopify/company_shopify_integration.py +0 -62
- letschatty/models/company/integrations/shopify/shopify_product_sync_status.py +0 -18
- letschatty/models/company/integrations/shopify/shopify_webhook_topics.py +0 -40
- letschatty/models/company/integrations/sync_status_enum.py +0 -9
- {letschatty-0.4.344.dist-info → letschatty-0.4.345.dist-info}/LICENSE +0 -0
- {letschatty-0.4.344.dist-info → letschatty-0.4.345.dist-info}/WHEEL +0 -0
|
@@ -61,23 +61,10 @@ class SmartTaggingCallbackEvent(LambdaAiEvent):
|
|
|
61
61
|
callback_metadata: SmartTaggingCallbackMetadata
|
|
62
62
|
|
|
63
63
|
@model_validator(mode="before")
|
|
64
|
-
def
|
|
65
|
-
if
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
data = values.get("data")
|
|
69
|
-
if isinstance(data, str):
|
|
70
|
-
import json
|
|
71
|
-
try:
|
|
72
|
-
data = json.loads(data)
|
|
73
|
-
values["data"] = data
|
|
74
|
-
except (json.JSONDecodeError, TypeError):
|
|
75
|
-
return values
|
|
76
|
-
|
|
77
|
-
if isinstance(data, dict) and "chain_of_thought" in data:
|
|
78
|
-
data["chain_of_thought"]["chatty_ai_agent_id"] = "000000000000000000000000"
|
|
79
|
-
|
|
80
|
-
return values
|
|
64
|
+
def validate_data(cls, data):
|
|
65
|
+
if isinstance(data, dict) and "data" in data and "chain_of_thought" in data["data"]:
|
|
66
|
+
data["data"]["chain_of_thought"]["chatty_ai_agent_id"] = "000000000000000000000000"
|
|
67
|
+
return data
|
|
81
68
|
|
|
82
69
|
class ChatData(BaseModel):
|
|
83
70
|
chat_id: StrObjectId
|
|
@@ -3,6 +3,7 @@ from .chat_based_events.contact_point import ContactPointEvent, ContactPointData
|
|
|
3
3
|
from .chat_based_events.business_area import ChatBusinessAreaEvent, BusinessAreaData
|
|
4
4
|
from .chat_based_events.workflow import WorkflowEvent, WorkflowEventData
|
|
5
5
|
from .chat_based_events.chat_status import ChatStatusEvent, ChatStatusEventData, ChatStatusModification, ChatCreatedFrom
|
|
6
|
+
from .chat_based_events.chat_client import ChatClientEvent
|
|
6
7
|
from .chat_based_events.chat_funnel import ChatFunnelEvent, FunnelEventData
|
|
7
8
|
from ...company.CRM.funnel import ChatFunnel, StageTransition, ActiveFunnel
|
|
8
9
|
from .company_based_events.user_events import UserEvent, UserEventData
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ..base import Event
|
|
2
|
+
from ..event_types import EventType
|
|
3
|
+
from .chat_based_event import CustomerEventData
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ChatClientEvent(Event):
|
|
9
|
+
"""Emitted when the client (contact/customer) data on a chat is updated."""
|
|
10
|
+
data: CustomerEventData
|
|
11
|
+
|
|
12
|
+
VALID_TYPES: ClassVar[set] = {
|
|
13
|
+
EventType.CHAT_CLIENT_UPDATED,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
def model_dump_json(self, *args, **kwargs):
|
|
17
|
+
dump = json.loads(super().model_dump_json(*args, **kwargs))
|
|
18
|
+
dump['data'] = self.data.model_dump_json()
|
|
19
|
+
return dump
|
|
@@ -8,6 +8,7 @@ EVENT_TO_TYPE_CLASSES = {
|
|
|
8
8
|
EventType.CHAT_CREATED : ChatStatusEvent,
|
|
9
9
|
EventType.CHAT_STATUS_UPDATED : ChatStatusEvent,
|
|
10
10
|
EventType.CHAT_DELETED : ChatStatusEvent,
|
|
11
|
+
EventType.CHAT_CLIENT_UPDATED : ChatClientEvent,
|
|
11
12
|
#TAGS
|
|
12
13
|
EventType.TAG_ASSIGNED : TagChatEvent,
|
|
13
14
|
EventType.TAG_REMOVED : TagChatEvent,
|
|
@@ -5,6 +5,8 @@ class EventType(StrEnum):
|
|
|
5
5
|
CHAT_CREATED = "chat.created"
|
|
6
6
|
CHAT_STATUS_UPDATED = "chat.status_updated"
|
|
7
7
|
CHAT_DELETED = "chat.deleted"
|
|
8
|
+
##CHAT CLIENT EVENTS
|
|
9
|
+
CHAT_CLIENT_UPDATED = "chat.client.updated"
|
|
8
10
|
#TAGS
|
|
9
11
|
TAG_ASSIGNED = "chat.tag.assigned"
|
|
10
12
|
TAG_REMOVED = "chat.tag.removed"
|
letschatty/models/chat/chat.py
CHANGED
|
@@ -269,17 +269,7 @@ class Chat(CompanyAssetModel):
|
|
|
269
269
|
@property
|
|
270
270
|
def bought_product_ids(self) -> List[StrObjectId]:
|
|
271
271
|
"""Get all sale ids in the chat"""
|
|
272
|
-
|
|
273
|
-
for sale in self.sales:
|
|
274
|
-
sale_product_ids = getattr(sale, "product_ids", None)
|
|
275
|
-
if sale_product_ids:
|
|
276
|
-
for product_id in sale_product_ids:
|
|
277
|
-
if product_id not in product_ids:
|
|
278
|
-
product_ids.append(product_id)
|
|
279
|
-
elif sale.product_id:
|
|
280
|
-
if sale.product_id not in product_ids:
|
|
281
|
-
product_ids.append(sale.product_id)
|
|
282
|
-
return product_ids
|
|
272
|
+
return [sale.product_id for sale in self.sales]
|
|
283
273
|
|
|
284
274
|
@property
|
|
285
275
|
def products(self) -> List[AssignedAssetToChat]:
|
|
@@ -376,3 +366,4 @@ class Chat(CompanyAssetModel):
|
|
|
376
366
|
dump["last_message"] = self.last_message.model_dump_json(serializer=SerializerType.DATABASE) if self.last_message else None
|
|
377
367
|
dump["flow_states"] = [flow_state.model_dump_json(serializer=SerializerType.DATABASE) for flow_state in self.flow_states]
|
|
378
368
|
return dump
|
|
369
|
+
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
from .assets import *
|
|
2
2
|
from .empresa import EmpresaModel
|
|
3
3
|
from .form_field import FormField, FormFieldPreview, CollectedData, SystemFormFields
|
|
4
|
-
from .CRM.funnel import Funnel, FunnelPreview, ChatFunnel, StageTransition, FunnelStatus
|
|
5
|
-
from .integrations.product_sync_status import ProductSyncStatus
|
|
6
|
-
from .integrations.sync_status_enum import SyncStatusEnum
|
|
7
|
-
from .integrations.shopify.shopify_webhook_topics import ShopifyWebhookTopic
|
|
8
|
-
from .integrations.shopify.company_shopify_integration import ShopifyIntegration
|
|
9
|
-
from .integrations.shopify.shopify_product_sync_status import ShopifyProductSyncStatus, ShopifyProductSyncStatusEnum
|
|
4
|
+
from .CRM.funnel import Funnel, FunnelPreview, ChatFunnel, StageTransition, FunnelStatus
|
|
@@ -84,13 +84,12 @@ class ChainOfThoughtInChatPreview(ChattyAssetPreview):
|
|
|
84
84
|
# trigger_id removed - trigger info is in: incoming_message_ids for user_message,
|
|
85
85
|
# triggered_by_user_id for manual_trigger, smart_follow_up_id for follow_up
|
|
86
86
|
chain_of_thought : Optional[str] = Field(default=None, description="The chain of thought")
|
|
87
|
-
confidence: Optional[int] = Field(default=None, ge=0, le=100, description="Confidence 0-100")
|
|
88
87
|
status: Optional[ChainOfThoughtInChatStatus] = Field(default=None, description="The status of the chain of thought")
|
|
89
88
|
name: str = Field(description="A title for the chain of thought")
|
|
90
89
|
|
|
91
90
|
@classmethod
|
|
92
91
|
def get_projection(cls) -> dict[str, Any]:
|
|
93
|
-
return super().get_projection() | {"chat_id": 1, "trigger": 1, "chain_of_thought": 1, "
|
|
92
|
+
return super().get_projection() | {"chat_id": 1, "trigger": 1, "chain_of_thought": 1, "name": 1, "status": 1}
|
|
94
93
|
|
|
95
94
|
@classmethod
|
|
96
95
|
def from_dict(cls, data: dict) -> 'ChainOfThoughtInChatPreview':
|
|
@@ -104,7 +103,6 @@ class ChainOfThoughtInChatPreview(ChattyAssetPreview):
|
|
|
104
103
|
chat_id=asset.chat_id,
|
|
105
104
|
trigger=asset.trigger,
|
|
106
105
|
chain_of_thought=asset.chain_of_thought,
|
|
107
|
-
confidence=asset.confidence,
|
|
108
106
|
status=asset.status,
|
|
109
107
|
company_id=asset.company_id,
|
|
110
108
|
created_at=asset.created_at,
|
|
@@ -123,7 +121,6 @@ class ChainOfThoughtInChat(CompanyAssetModel):
|
|
|
123
121
|
chatty_ai_agent_id : StrObjectId = Field(description="The chatty ai agent id")
|
|
124
122
|
chatty_ai_agent : Dict[str, Any] = Field(description="The chatty ai agent at the moment of triggering the chain of thought", exclude=True,default_factory=dict)
|
|
125
123
|
chain_of_thought : Optional[str] = Field(default=None, description="The chain of thought")
|
|
126
|
-
confidence: Optional[int] = Field(default=None, ge=0, le=100, description="Confidence 0-100")
|
|
127
124
|
name: str = Field(description="A title for the chain of thought", alias="title")
|
|
128
125
|
preview_class: ClassVar[Type[ChattyAssetPreview]] = ChainOfThoughtInChatPreview
|
|
129
126
|
|
|
@@ -131,4 +128,5 @@ class ChainOfThoughtInChat(CompanyAssetModel):
|
|
|
131
128
|
if self.chain_of_thought is None:
|
|
132
129
|
self.chain_of_thought = cot
|
|
133
130
|
else:
|
|
134
|
-
self.chain_of_thought += f"\n{cot}"
|
|
131
|
+
self.chain_of_thought += f"\n{cot}"
|
|
132
|
+
|
|
@@ -7,16 +7,15 @@ independently without loading entire chat documents.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from letschatty.models.analytics.events.base import EventType
|
|
10
|
-
from enum import StrEnum
|
|
11
10
|
from pydantic import BaseModel, Field, field_validator, ConfigDict
|
|
12
11
|
from datetime import datetime
|
|
13
12
|
from zoneinfo import ZoneInfo
|
|
14
13
|
from typing import Optional, ClassVar, List, Dict, Any
|
|
14
|
+
from enum import StrEnum
|
|
15
15
|
from letschatty.models.base_models.chatty_asset_model import CompanyAssetModel, ChattyAssetPreview
|
|
16
16
|
from letschatty.models.utils.types.identifier import StrObjectId
|
|
17
17
|
from .chain_of_thought_in_chat import ChainOfThoughtInChatTrigger
|
|
18
18
|
from .chatty_ai_mode import ChattyAIMode
|
|
19
|
-
from .statuses import DataCollectionStatus, PreQualifyStatus
|
|
20
19
|
import logging
|
|
21
20
|
|
|
22
21
|
logger = logging.getLogger(__name__)
|
|
@@ -46,6 +45,38 @@ class HumanInterventionReason(StrEnum):
|
|
|
46
45
|
SOMETHING_WENT_WRONG = "something_went_wrong"
|
|
47
46
|
|
|
48
47
|
|
|
48
|
+
class DataCollectionStatus(StrEnum):
|
|
49
|
+
"""
|
|
50
|
+
Status of data collection for the AI agent in this chat.
|
|
51
|
+
Only tracks field collection, not qualification status.
|
|
52
|
+
|
|
53
|
+
- COLLECTING: Still collecting data from the user
|
|
54
|
+
- MANDATORY_COMPLETED: All mandatory fields have been collected
|
|
55
|
+
- ALL_COMPLETED: All fields (mandatory + optional) have been collected
|
|
56
|
+
- CANCELLED: Data collection was cancelled
|
|
57
|
+
"""
|
|
58
|
+
COLLECTING = "collecting"
|
|
59
|
+
MANDATORY_COMPLETED = "mandatory_completed"
|
|
60
|
+
ALL_COMPLETED = "all_completed"
|
|
61
|
+
CANCELLED = "cancelled"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PreQualifyStatus(StrEnum):
|
|
65
|
+
"""
|
|
66
|
+
Status of pre-qualification for the AI agent in this chat.
|
|
67
|
+
Separate from data collection - tracks qualification evaluation.
|
|
68
|
+
|
|
69
|
+
- PENDING: Waiting for data collection to complete
|
|
70
|
+
- EVALUATING: Data collected, evaluating acceptance criteria
|
|
71
|
+
- QUALIFIED: User met acceptance criteria
|
|
72
|
+
- UNQUALIFIED: User did NOT meet acceptance criteria
|
|
73
|
+
"""
|
|
74
|
+
PENDING = "pending"
|
|
75
|
+
EVALUATING = "evaluating"
|
|
76
|
+
QUALIFIED = "qualified"
|
|
77
|
+
UNQUALIFIED = "unqualified"
|
|
78
|
+
|
|
79
|
+
|
|
49
80
|
class HumanIntervention(BaseModel):
|
|
50
81
|
"""
|
|
51
82
|
Reason for human intervention
|
|
@@ -321,3 +352,4 @@ class ChattyAIAgentInChat(CompanyAssetModel):
|
|
|
321
352
|
"""Cancel data collection"""
|
|
322
353
|
self.data_collection_status = DataCollectionStatus.CANCELLED
|
|
323
354
|
self.updated_at = datetime.now(ZoneInfo("UTC"))
|
|
355
|
+
|
|
@@ -4,13 +4,12 @@ from enum import StrEnum
|
|
|
4
4
|
from pydantic_core.core_schema import str_schema
|
|
5
5
|
|
|
6
6
|
from letschatty.models.company.assets.ai_agents_v2.chain_of_thought_in_chat import ChainOfThoughtInChatTrigger
|
|
7
|
-
from letschatty.models.company.assets.ai_agents_v2.statuses import DataCollectionStatus, PreQualifyStatus
|
|
8
7
|
from ...utils.types.identifier import StrObjectId
|
|
9
8
|
from datetime import datetime
|
|
10
9
|
from zoneinfo import ZoneInfo
|
|
11
10
|
from bson import ObjectId
|
|
12
11
|
import json
|
|
13
|
-
from typing import Dict, Any, Optional
|
|
12
|
+
from typing import Dict, Any, Optional
|
|
14
13
|
from letschatty.models.utils.types.serializer_type import SerializerType
|
|
15
14
|
from letschatty.models.company.assets.ai_agents_v2.chatty_ai_mode import ChattyAIMode
|
|
16
15
|
|
|
@@ -67,8 +66,7 @@ class AssignedAssetToChat(BaseModel):
|
|
|
67
66
|
|
|
68
67
|
|
|
69
68
|
class SaleAssignedToChat(AssignedAssetToChat):
|
|
70
|
-
product_id:
|
|
71
|
-
product_ids: List[StrObjectId] = Field(default_factory=list)
|
|
69
|
+
product_id: StrObjectId = Field(frozen=True)
|
|
72
70
|
|
|
73
71
|
class ContactPointAssignedToChat(AssignedAssetToChat):
|
|
74
72
|
source_id: StrObjectId = Field(frozen=True)
|
|
@@ -77,14 +75,6 @@ class ChattyAIAgentAssignedToChat(AssignedAssetToChat):
|
|
|
77
75
|
mode: ChattyAIMode = Field(default=ChattyAIMode.OFF)
|
|
78
76
|
requires_human_intervention: bool = Field(default=False)
|
|
79
77
|
is_processing: bool = Field(default=False)
|
|
80
|
-
data_collection_status: Optional[DataCollectionStatus] = Field(
|
|
81
|
-
default=None,
|
|
82
|
-
description="Status of data collection for pre-qualification"
|
|
83
|
-
)
|
|
84
|
-
pre_qualify_status: Optional[PreQualifyStatus] = Field(
|
|
85
|
-
default=None,
|
|
86
|
-
description="Status of pre-qualification"
|
|
87
|
-
)
|
|
88
78
|
last_call_started_at: Optional[datetime] = Field(default=None, description="The timestamp of the get chat with prompt (the moment n8n started processing the call)")
|
|
89
79
|
trigger_timestamp: Optional[datetime] = Field(default=None, description="The timestamp of the trigger that started the call, if it's a manual trigger, it will be the timestamp of the manual trigger, if it's a follow up, it will be the timestamp of the follow up, if it's a user message, it will be the timestamp of the user message")
|
|
90
80
|
last_call_cot_id: Optional[StrObjectId] = Field(default=None)
|
|
@@ -5,7 +5,7 @@ from ...utils.types.identifier import StrObjectId
|
|
|
5
5
|
|
|
6
6
|
class Sale(CompanyAssetModel):
|
|
7
7
|
chat_id: StrObjectId
|
|
8
|
-
product_id:
|
|
8
|
+
product_id: StrObjectId
|
|
9
9
|
quantity: int
|
|
10
10
|
total_amount: float
|
|
11
11
|
currency: str
|
|
@@ -16,7 +16,7 @@ class Sale(CompanyAssetModel):
|
|
|
16
16
|
creator_id: StrObjectId
|
|
17
17
|
|
|
18
18
|
class SaleRequest(BaseModel):
|
|
19
|
-
product_id:
|
|
19
|
+
product_id: StrObjectId
|
|
20
20
|
quantity: int
|
|
21
21
|
creator_id: StrObjectId
|
|
22
22
|
total_amount: float
|
|
@@ -36,4 +36,4 @@ class SaleRequest(BaseModel):
|
|
|
36
36
|
"paid_amount": 100,
|
|
37
37
|
"installments": 1,
|
|
38
38
|
"details": {}
|
|
39
|
-
}
|
|
39
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from letschatty.models.company.integrations.shopify.company_shopify_integration import ShopifyIntegration
|
|
2
1
|
from pydantic import Field, ConfigDict, field_validator, SecretStr, model_validator
|
|
3
2
|
from typing import Optional, List, Dict
|
|
4
3
|
|
|
@@ -35,7 +34,7 @@ class EmpresaModel(ChattyAssetModel):
|
|
|
35
34
|
continuous_conversation_template_name: Optional[str] = Field(default = None, description="The name of the continuous conversation template")
|
|
36
35
|
default_follow_up_strategy_id: Optional[StrObjectId] = Field(default = None, description="The id of the default follow up strategy")
|
|
37
36
|
messaging_settings: MessagingSettings = Field(default = MessagingSettings(), description="The messaging settings for the company")
|
|
38
|
-
|
|
37
|
+
|
|
39
38
|
|
|
40
39
|
model_config = ConfigDict(
|
|
41
40
|
validate_by_name=True,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from pydantic import BaseModel, Field, field_validator
|
|
1
|
+
from pydantic import BaseModel, Field, field_validator
|
|
2
2
|
from typing import Optional, Any, ClassVar, List
|
|
3
3
|
from letschatty.models.base_models.chatty_asset_model import CompanyAssetModel, ChattyAssetPreview
|
|
4
4
|
import re
|
|
@@ -212,11 +212,7 @@ class CollectedData(BaseModel):
|
|
|
212
212
|
name: Optional[str] = Field(default=None, description="Customer's name")
|
|
213
213
|
email: Optional[str] = Field(default=None, description="Customer's email address")
|
|
214
214
|
phone: Optional[str] = Field(default=None, description="Customer's phone number")
|
|
215
|
-
document_id: Optional[str] = Field(
|
|
216
|
-
default=None,
|
|
217
|
-
alias="dni",
|
|
218
|
-
description="Customer's DNI/ID number"
|
|
219
|
-
)
|
|
215
|
+
document_id: Optional[str] = Field(default=None, description="Customer's DNI/ID number")
|
|
220
216
|
|
|
221
217
|
# Generic key-value store for any other collected fields
|
|
222
218
|
additional_fields: dict[str, Any] = Field(
|
|
@@ -224,9 +220,6 @@ class CollectedData(BaseModel):
|
|
|
224
220
|
description="Additional collected fields as key-value pairs"
|
|
225
221
|
)
|
|
226
222
|
|
|
227
|
-
model_config = ConfigDict(
|
|
228
|
-
populate_by_name=True
|
|
229
|
-
)
|
|
230
223
|
|
|
231
224
|
@classmethod
|
|
232
225
|
def example(cls) -> dict:
|
|
@@ -28,10 +28,9 @@ from ...models.chat.scheduled_messages import ScheduledMessageStatus
|
|
|
28
28
|
from ...models.utils.types.identifier import StrObjectId
|
|
29
29
|
from ...models.utils.custom_exceptions.custom_exceptions import AssetAlreadyAssigned, MessageNotFoundError, NotFoundError, MessageAlreadyInChat, MetaErrorNotification, ChatAlreadyAssigned, AlreadyCompleted, ErrorToMantainSafety
|
|
30
30
|
from ..factories.messages.central_notification_factory import CentralNotificationFactory
|
|
31
|
-
from ..factories.messages.chatty_message_factory import from_message_draft
|
|
32
31
|
from ...models.messages.chatty_messages.base.message_draft import ChattyContentAudio, MessageDraft
|
|
33
32
|
from ...models.messages.chatty_messages.schema.chatty_content.content_central import CentralNotificationStatus
|
|
34
|
-
from ...models.messages.chatty_messages.schema import ChattyContext
|
|
33
|
+
from ...models.messages.chatty_messages.schema import ChattyContext
|
|
35
34
|
from ...models.utils.types.message_types import MessageType
|
|
36
35
|
from .conversation_topics_service import ConversationTopicsService
|
|
37
36
|
import logging
|
|
@@ -212,43 +211,6 @@ class ChatService:
|
|
|
212
211
|
ChatService.add_central_notification_from_text(chat=chat, body=f"Agente de IA {chatty_ai_agent.name} actualizado en el chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.CHATTY_AI_AGENT_UPDATED)
|
|
213
212
|
return chat.chatty_ai_agent
|
|
214
213
|
|
|
215
|
-
@staticmethod
|
|
216
|
-
def escalate_chatty_ai_agent(
|
|
217
|
-
chat: Chat,
|
|
218
|
-
execution_context: ExecutionContext,
|
|
219
|
-
message: Optional[str] = None,
|
|
220
|
-
reason: Optional[str] = None
|
|
221
|
-
) -> None:
|
|
222
|
-
"""
|
|
223
|
-
Mark the chat's AI agent as requiring human intervention and add a central notification.
|
|
224
|
-
"""
|
|
225
|
-
if chat.chatty_ai_agent and not chat.chatty_ai_agent.requires_human_intervention:
|
|
226
|
-
chat.chatty_ai_agent.requires_human_intervention = True
|
|
227
|
-
execution_context.set_event_time(datetime.now(tz=ZoneInfo("UTC")))
|
|
228
|
-
body = "El chat fue escalado a un agente humano"
|
|
229
|
-
if reason:
|
|
230
|
-
body = f"{body}. Motivo: {reason}"
|
|
231
|
-
ChatService.add_central_notification_from_text(
|
|
232
|
-
chat=chat,
|
|
233
|
-
body=body,
|
|
234
|
-
subtype=MessageSubtype.CHATTY_AI_AGENT_NOTIFICATION,
|
|
235
|
-
content_status=CentralNotificationStatus.WARNING,
|
|
236
|
-
context=ChattyContext(chain_of_thought_id=execution_context.chain_of_thought_id)
|
|
237
|
-
)
|
|
238
|
-
if message:
|
|
239
|
-
outgoing_context = ChattyContext(chain_of_thought_id=execution_context.chain_of_thought_id)
|
|
240
|
-
outgoing_message = from_message_draft(
|
|
241
|
-
MessageDraft(
|
|
242
|
-
type=MessageType.TEXT,
|
|
243
|
-
content=ChattyContentText(body=message),
|
|
244
|
-
context=outgoing_context,
|
|
245
|
-
subtype=MessageSubtype.NONE,
|
|
246
|
-
is_incoming_message=False
|
|
247
|
-
),
|
|
248
|
-
sent_by=execution_context.executor.id
|
|
249
|
-
)
|
|
250
|
-
ChatService.add_message(chat=chat, message=outgoing_message)
|
|
251
|
-
|
|
252
214
|
@staticmethod
|
|
253
215
|
def add_workflow_link(chat : Chat, link : LinkItem, flow:FlowPreview, execution_context: ExecutionContext, description: str, last_incoming_message_id: Optional[str] = None, next_call: Optional[datetime] = None) -> FlowStateAssignedToChat:
|
|
254
216
|
"""
|
|
@@ -304,46 +266,36 @@ class ChatService:
|
|
|
304
266
|
return next((state for state in chat.flow_states if state.is_smart_follow_up), None)
|
|
305
267
|
|
|
306
268
|
@staticmethod
|
|
307
|
-
def create_sale(
|
|
308
|
-
chat: Chat,
|
|
309
|
-
execution_context: ExecutionContext,
|
|
310
|
-
sale: Sale,
|
|
311
|
-
product: Optional[Product],
|
|
312
|
-
product_ids: Optional[List[StrObjectId]] = None,
|
|
313
|
-
product_label: Optional[str] = None
|
|
314
|
-
) -> SaleAssignedToChat:
|
|
269
|
+
def create_sale(chat : Chat, execution_context: ExecutionContext, sale : Sale, product : Product) -> SaleAssignedToChat:
|
|
315
270
|
"""
|
|
316
271
|
Add a sale to the chat.
|
|
317
272
|
"""
|
|
318
273
|
if next((sale for sale in chat.client.sales if sale.asset_id == sale.id), None) is not None:
|
|
319
274
|
raise AssetAlreadyAssigned(f"Sale with id {sale.id} already assigned to chat {chat.id}")
|
|
320
|
-
label = product_label or (product.name if product else "multiples productos")
|
|
321
|
-
assigned_product_ids = product_ids or ([product.id] if product else [])
|
|
322
275
|
assigned_asset = SaleAssignedToChat(
|
|
323
276
|
asset_type=ChatAssetType.SALE,
|
|
324
277
|
asset_id=sale.id,
|
|
325
278
|
assigned_at=sale.created_at,
|
|
326
279
|
assigned_by=execution_context.executor.id,
|
|
327
|
-
product_id=product.id
|
|
328
|
-
product_ids=assigned_product_ids
|
|
280
|
+
product_id=product.id
|
|
329
281
|
)
|
|
330
282
|
execution_context.set_event_time(assigned_asset.assigned_at)
|
|
331
283
|
bisect.insort(chat.client.sales, assigned_asset)
|
|
332
|
-
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta de {
|
|
333
|
-
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {
|
|
284
|
+
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta de {product.name}", description=f"Venta de {product.name} creada por {execution_context.executor.name}", starred=False, subtype=MessageSubtype.SALE_ADDED))
|
|
285
|
+
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {product.name} agregada al chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.SALE_ADDED)
|
|
334
286
|
return assigned_asset
|
|
335
287
|
|
|
336
288
|
@staticmethod
|
|
337
|
-
def update_sale(chat: Chat, execution_context: ExecutionContext, sale: Sale,
|
|
289
|
+
def update_sale(chat : Chat, execution_context: ExecutionContext, sale : Sale, product : Product) -> Sale:
|
|
338
290
|
"""
|
|
339
291
|
Update a sale for the chat.
|
|
340
292
|
"""
|
|
341
|
-
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta actualizada de {
|
|
342
|
-
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {
|
|
293
|
+
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta actualizada de {product.name}", description=f"Venta de {product.name} actualizada por {execution_context.executor.name}", starred=False, subtype=MessageSubtype.SALE_UPDATED))
|
|
294
|
+
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {product.name} actualizada en el chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.SALE_UPDATED)
|
|
343
295
|
return sale
|
|
344
296
|
|
|
345
297
|
@staticmethod
|
|
346
|
-
def delete_sale(chat: Chat, execution_context: ExecutionContext, sale_id: StrObjectId,
|
|
298
|
+
def delete_sale(chat : Chat, execution_context: ExecutionContext, sale_id : StrObjectId, product : Product) -> SaleAssignedToChat:
|
|
347
299
|
"""
|
|
348
300
|
Logically remove a sale from the chat.
|
|
349
301
|
"""
|
|
@@ -351,8 +303,8 @@ class ChatService:
|
|
|
351
303
|
assigned_asset_to_remove = next(sale for sale in chat.client.sales if sale.asset_id == sale_id)
|
|
352
304
|
chat.client.sales.remove(assigned_asset_to_remove)
|
|
353
305
|
execution_context.set_event_time(datetime.now(tz=ZoneInfo("UTC")))
|
|
354
|
-
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta eliminada de {
|
|
355
|
-
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {
|
|
306
|
+
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta eliminada de {product.name}", description=f"Venta de {product.name} eliminada por {execution_context.executor.name}", starred=False, subtype=MessageSubtype.SALE_DELETED))
|
|
307
|
+
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {product.name} eliminada del chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.SALE_DELETED)
|
|
356
308
|
return assigned_asset_to_remove
|
|
357
309
|
except StopIteration:
|
|
358
310
|
raise NotFoundError(message=f"Sale with id {sale_id} not found in chat {chat.id}")
|
|
@@ -897,13 +849,6 @@ class ChatService:
|
|
|
897
849
|
chat.client.email = collected_data.email
|
|
898
850
|
updated_fields.append("email")
|
|
899
851
|
|
|
900
|
-
if collected_data.phone:
|
|
901
|
-
if chat.client.lead_form_data is None:
|
|
902
|
-
chat.client.lead_form_data = {}
|
|
903
|
-
if chat.client.lead_form_data.get("phone") != collected_data.phone:
|
|
904
|
-
chat.client.lead_form_data["phone"] = collected_data.phone
|
|
905
|
-
updated_fields.append("phone")
|
|
906
|
-
|
|
907
852
|
if collected_data.document_id and chat.client.document_id != collected_data.document_id:
|
|
908
853
|
chat.client.document_id = collected_data.document_id
|
|
909
854
|
updated_fields.append("document_id")
|
|
@@ -921,18 +866,11 @@ class ChatService:
|
|
|
921
866
|
execution_context.set_event_time(datetime.now(tz=ZoneInfo("UTC")))
|
|
922
867
|
logger.info(f"Updated collected data for chat {chat.id}: {', '.join(updated_fields)}")
|
|
923
868
|
|
|
924
|
-
field_label_map = {
|
|
925
|
-
"name": "nombre",
|
|
926
|
-
"email": "email",
|
|
927
|
-
"phone": "telefono",
|
|
928
|
-
"document_id": "dni",
|
|
929
|
-
}
|
|
930
|
-
display_fields = [field_label_map.get(field, field) for field in updated_fields]
|
|
931
869
|
ChatService.add_central_notification_from_text(
|
|
932
870
|
chat=chat,
|
|
933
|
-
body=f"
|
|
871
|
+
body=f"Collected customer data: {', '.join(updated_fields)}",
|
|
934
872
|
subtype=MessageSubtype.CLIENT_INFO_UPDATED,
|
|
935
873
|
context=ChattyContext(chain_of_thought_id=execution_context.chain_of_thought_id)
|
|
936
874
|
)
|
|
937
875
|
|
|
938
|
-
return chat.client.lead_form_data
|
|
876
|
+
return chat.client.lead_form_data
|
|
@@ -415,6 +415,30 @@ class EventFactory:
|
|
|
415
415
|
events.append(event)
|
|
416
416
|
return events
|
|
417
417
|
|
|
418
|
+
@staticmethod
|
|
419
|
+
def chat_client_updated_events(chat_with_assets: ChatWithAssets, company_info: EmpresaModel, trace_id: str, executor_type: ExecutorType, executor_id: StrObjectId, event_type: EventType, time: datetime, company_snapshot: Optional[CompanyChatsSnapshot] = None, agent_snapshot: Optional[AgentChatsSnapshot] = None) -> List[Event]:
|
|
420
|
+
events = []
|
|
421
|
+
base_data = EventFactory._create_base_customer_event_data(
|
|
422
|
+
chat_with_assets=chat_with_assets,
|
|
423
|
+
company_info=company_info,
|
|
424
|
+
executor_type=executor_type,
|
|
425
|
+
executor_id=executor_id,
|
|
426
|
+
company_snapshot=company_snapshot,
|
|
427
|
+
agent_snapshot=agent_snapshot
|
|
428
|
+
)
|
|
429
|
+
event = ChatClientEvent(
|
|
430
|
+
specversion=EventFactory.package_version(),
|
|
431
|
+
type=event_type,
|
|
432
|
+
time=time,
|
|
433
|
+
data=base_data,
|
|
434
|
+
source="chatty_api.webapp",
|
|
435
|
+
company_id=company_info.id,
|
|
436
|
+
frozen_company_name=company_info.frozen_name,
|
|
437
|
+
trace_id=trace_id
|
|
438
|
+
)
|
|
439
|
+
events.append(event)
|
|
440
|
+
return events
|
|
441
|
+
|
|
418
442
|
@staticmethod
|
|
419
443
|
def chat_created_events(chat_with_assets: ChatWithAssets, company_info: EmpresaModel, trace_id: str,executor_type: ExecutorType, executor_id: StrObjectId, event_type: EventType, time: datetime, contact_point_id: Optional[StrObjectId], created_from: ChatCreatedFrom, company_snapshot: Optional[CompanyChatsSnapshot] = None, agent_snapshot: Optional[AgentChatsSnapshot] = None) -> List[Event]:
|
|
420
444
|
events = []
|
|
@@ -10,10 +10,6 @@ if TYPE_CHECKING:
|
|
|
10
10
|
|
|
11
11
|
class SourcesAndTopicsValidator:
|
|
12
12
|
"""This class provides methods to validate sources and topics."""
|
|
13
|
-
@staticmethod
|
|
14
|
-
def normalize_source_name(name: str) -> str:
|
|
15
|
-
return name.strip().lower()
|
|
16
|
-
|
|
17
13
|
@staticmethod
|
|
18
14
|
def source_validation_check(sources : Dict[str,Source], topics : Dict[str,Topic], source : Source, existing_source_id : str | None = None) -> None:
|
|
19
15
|
"""Checks validation of a source against other sources and topics."""
|
|
@@ -32,16 +28,9 @@ class SourcesAndTopicsValidator:
|
|
|
32
28
|
"""This method checks that no duplicated source is created.
|
|
33
29
|
For Pure Ads, it checks that the ad_id is unique and doesn't exist already.
|
|
34
30
|
For Other Sources, it checks that exact trigger doesn't exist already. """
|
|
35
|
-
normalized_name = SourcesAndTopicsValidator.normalize_source_name(source.name)
|
|
36
|
-
existing_source_name = None
|
|
37
|
-
if existing_source_id and existing_source_id in sources:
|
|
38
|
-
existing_source_name = SourcesAndTopicsValidator.normalize_source_name(sources[existing_source_id].name)
|
|
39
|
-
should_check_name = existing_source_name is None or normalized_name != existing_source_name
|
|
40
31
|
for source_id, source_to_check in sources.items():
|
|
41
32
|
if source_id == existing_source_id:
|
|
42
33
|
continue
|
|
43
|
-
if should_check_name and SourcesAndTopicsValidator.normalize_source_name(source_to_check.name) == normalized_name:
|
|
44
|
-
raise ConflictedSource(f":warning: *Conflict while adding source: Name already exists* \n New source '{source.name}' \n- Id {source.id} \n Existing source '{source_to_check.name}' \n- Id {source_to_check.id}", conflicting_source_id=source_to_check.id)
|
|
45
34
|
if source == source_to_check: #type: ignore
|
|
46
35
|
if isinstance(source, OtherSource):
|
|
47
36
|
raise ConflictedSource(f":warning: *Conflict while adding source: Trigger already exists* \n New source '{source.name}' \n- Id {source.id} \n- Trigger {source.trigger[:30]} \n Existing source '{source_to_check.name}' \n- Id {source_to_check.id} \n- Trigger '{source_to_check.trigger[:30]}'", conflicting_source_id=source_to_check.id)
|
|
@@ -2,16 +2,17 @@ letschatty/__init__.py,sha256=6dGYdy5edB1dHdgvpqUpENZ347CpIyWgR1CsGYwPSk8,45
|
|
|
2
2
|
letschatty/models/__init__.py,sha256=obWHa796C-O1mqo_lk22KhUC2ODVvG7cICY3Lo-AqJg,126
|
|
3
3
|
letschatty/models/ai_microservices/__init__.py,sha256=h0xLiuU1wfHpDl1oKCzVKUef3ROYU7qIyVxBsdcdZe0,641
|
|
4
4
|
letschatty/models/ai_microservices/expected_output.py,sha256=PWXvmGiOhqAuOVCZHhfvUMzb8BwSnAoys4CReJ9dIK4,9441
|
|
5
|
-
letschatty/models/ai_microservices/lambda_events.py,sha256=
|
|
5
|
+
letschatty/models/ai_microservices/lambda_events.py,sha256=aazW8g6M7b7Lk-KZbx0R4AoV5oMjgXEpAcnsIwQ7Jf4,14319
|
|
6
6
|
letschatty/models/ai_microservices/lambda_invokation_types.py,sha256=KWvXFvHhwYMaAIHbGpdW8AYE0eqrfCgZ_28jIyGXoME,1918
|
|
7
7
|
letschatty/models/ai_microservices/n8n_ai_agents_payload.py,sha256=E5tu2UcSJCybMSvxfZG3JIxSxHbTBbjdRE74cnRtlGY,696
|
|
8
8
|
letschatty/models/ai_microservices/openai_payloads.py,sha256=HauUGf4c37khJJsKyH4ikxzHKboGWnerjDQgDOnMFrY,271
|
|
9
9
|
letschatty/models/analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
letschatty/models/analytics/events/__init__.py,sha256=
|
|
10
|
+
letschatty/models/analytics/events/__init__.py,sha256=7meuINSyULnUSRSVAWZWlFVNuBh9e0OtoH8FbzHKMr4,2155
|
|
11
11
|
letschatty/models/analytics/events/base.py,sha256=vmRnowUYot4OjYyqVjevGY9kF3Kb47IDkMJgJRwvcfU,1793
|
|
12
12
|
letschatty/models/analytics/events/chat_based_events/ai_agent_chat.py,sha256=Ac6Llg2IFM0lftCkAErThjd283f_AEYqg76OkNnihuM,1681
|
|
13
13
|
letschatty/models/analytics/events/chat_based_events/business_area.py,sha256=YZgIejCzbryySxolMSgvCFMuRggMcwBJ-jeQKdEshvM,904
|
|
14
14
|
letschatty/models/analytics/events/chat_based_events/chat_based_event.py,sha256=YDXzX78jZgxuPFkTqipXFAo6G0-ajl_RkJISsjM9YJU,1469
|
|
15
|
+
letschatty/models/analytics/events/chat_based_events/chat_client.py,sha256=Zqo9uownFMyu9w7i2pt5y1jRVzqiXGxCCTXIlsPjYsk,558
|
|
15
16
|
letschatty/models/analytics/events/chat_based_events/chat_context.py,sha256=Y6VfYorrKurbUgWtIPOsfNMbGQbuTPR5wQQLrbBxD9Q,2226
|
|
16
17
|
letschatty/models/analytics/events/chat_based_events/chat_funnel.py,sha256=LX_lcOn1R2VhdffdZeuc7S87PcoHBhEuppWaBXFP6tY,4184
|
|
17
18
|
letschatty/models/analytics/events/chat_based_events/chat_status.py,sha256=9vt8GP6QnMrUuJF-AbUQm68Nkc0Zgexmnb-T1k8tsD4,2222
|
|
@@ -28,8 +29,8 @@ letschatty/models/analytics/events/chat_based_events/workflow.py,sha256=BZQN2JDI
|
|
|
28
29
|
letschatty/models/analytics/events/company_based_events/asset_events.py,sha256=EtVhl_W3wxkJhflbyk4NQraxAdpLeuesc9IfyOC5QV8,3490
|
|
29
30
|
letschatty/models/analytics/events/company_based_events/company_events.py,sha256=PZAiw2imo706iM0EaCxnIaiFchMKivbwjKd7gsozA8k,1673
|
|
30
31
|
letschatty/models/analytics/events/company_based_events/user_events.py,sha256=ZUoBaS60s63SoTUOUunLpojP1BQ9Cdl4sl_KdgY6NGI,2466
|
|
31
|
-
letschatty/models/analytics/events/event_type_to_classes.py,sha256=
|
|
32
|
-
letschatty/models/analytics/events/event_types.py,sha256=
|
|
32
|
+
letschatty/models/analytics/events/event_type_to_classes.py,sha256=x5Ai3UZio9DeRcN2ag1ipLwUFFdInIKLZNhYgyYSvcM,4384
|
|
33
|
+
letschatty/models/analytics/events/event_types.py,sha256=h8CH0aXIuMHjd8mCQ2v3KED03cggysIEQvV4f93s-8c,5544
|
|
33
34
|
letschatty/models/analytics/metrics/__init_.py,sha256=DnGDYvEn9S1sSMe56e7mwE5r013nO-Fsf0pgBnf9RjI,276
|
|
34
35
|
letschatty/models/analytics/metrics/daily_contact_points.py,sha256=eWwUmhFpL-Xn5sWQWCcACrulb4xwjMY80jDSAWsTMb4,1524
|
|
35
36
|
letschatty/models/analytics/metrics/daily_new_chats.py,sha256=kTsQFDFwavTq3u7HcHx3RqZ_O517HqTrOD-k8q4JQz0,322
|
|
@@ -63,7 +64,7 @@ letschatty/models/base_models/update_model_interface.py,sha256=80YuvkEM5Rd3Ycysq
|
|
|
63
64
|
letschatty/models/base_models/validate_timestamps_and_id.py,sha256=WRtaoW2EYtItgZjnNOYAffxy9K7M_NMNJ5imRN8Iu80,1971
|
|
64
65
|
letschatty/models/channels/channel.py,sha256=Mgqigm-3uRDozxBIZZYj62PQwtnB0rAKoY2JS4477n8,2029
|
|
65
66
|
letschatty/models/chat/assets_assigned_to_chat.py,sha256=nAdSzRvUSWa8-Plw6KZKnpUxxjCdWs_grml0bhskSis,192
|
|
66
|
-
letschatty/models/chat/chat.py,sha256=
|
|
67
|
+
letschatty/models/chat/chat.py,sha256=8kAQvtH_ccrhk6lnPDdmRvBwvV_iBYsA2h7Q9NcdNOA,16298
|
|
67
68
|
letschatty/models/chat/chat_status_modifications.py,sha256=LcwXNl4WRllPI_ZYKcg5ANRjmSk4Cykkra0naayMAt4,317
|
|
68
69
|
letschatty/models/chat/chat_with_assets.py,sha256=K4UUwdfLw_Q0ZNHdeoqavqyHkZwisAQOi1OPMBYszOU,814
|
|
69
70
|
letschatty/models/chat/client.py,sha256=oa7PM76V-OiBJL4T-IMytPW0WZdtvh9vFZhNtRQvt6Q,2196
|
|
@@ -78,16 +79,16 @@ letschatty/models/chat/temporary_chat.py,sha256=MR68vYrNvoyf40cLs2p5NnI8BNeN1VGT
|
|
|
78
79
|
letschatty/models/chat/time_left.py,sha256=2uA9dS_0QAVnWn7Q2wIwGe4tMrlYBP9qySC7M8I0jb8,3307
|
|
79
80
|
letschatty/models/company/CRM/business_area.py,sha256=U0F_7Rbq-e5tMSj_MugPqMHm2-IHxbAKbN0PWZ_93-o,296
|
|
80
81
|
letschatty/models/company/CRM/funnel.py,sha256=gBGZhud060sK5QFI5y9NgmFuhLCOAtzK81TKBhK2gxE,15820
|
|
81
|
-
letschatty/models/company/__init__.py,sha256=
|
|
82
|
+
letschatty/models/company/__init__.py,sha256=N_I-kRMHrtnp00c_bbFSiYYM47HJRS-d5slgtbexsl4,229
|
|
82
83
|
letschatty/models/company/assets/__init__.py,sha256=z0xN_lt1D76bXt8DXVdbH3GkofUPQPZU9NioRSLt_lI,244
|
|
83
84
|
letschatty/models/company/assets/ai_agents_v2/ai_agent_message_draft.py,sha256=xbshA34RSjHm2g8J7hW2FkWo-Qm8MH2HTbcRcoYmyvc,2076
|
|
84
85
|
letschatty/models/company/assets/ai_agents_v2/ai_agents_decision_output.py,sha256=aEKZCOsGiFFPSx23fkS5Khfsxo-r8JGk3O0sxiGs8T0,5876
|
|
85
|
-
letschatty/models/company/assets/ai_agents_v2/chain_of_thought_in_chat.py,sha256=
|
|
86
|
+
letschatty/models/company/assets/ai_agents_v2/chain_of_thought_in_chat.py,sha256=LJqtSl1A1V0frPLYwW-5cMZxaGx7-0x1HvujRXsZsNU,5877
|
|
86
87
|
letschatty/models/company/assets/ai_agents_v2/chat_example.py,sha256=yCKt6ifNYch3C78zAvj8To0_Sb9CPAZ8sC-hyoBPa4s,1816
|
|
87
88
|
letschatty/models/company/assets/ai_agents_v2/chat_example_test.py,sha256=vChD-TkX1ORRD9LMbd2HlpbK4QyrsfhDiJd-LDjIqlk,4618
|
|
88
89
|
letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent.py,sha256=R2rCANri8ZiqnmPP10PWoanGddMImCMxrl78Y6d-194,8204
|
|
89
90
|
letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_config_for_automation.py,sha256=W8orpgp-yG8OHNWGQ16jMv-VgM_Z-Hk0q8PtC0KT2KU,388
|
|
90
|
-
letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_in_chat.py,sha256=
|
|
91
|
+
letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_in_chat.py,sha256=uVmD4FqGP5Tc8noxMBuRYGbkOXrtYlH2w6SAYwQZlLo,15494
|
|
91
92
|
letschatty/models/company/assets/ai_agents_v2/chatty_ai_mode.py,sha256=CKlKI3t_D5eWJPn6hRvN4_V8Wu6W8c0x_EFQlpA50F4,2521
|
|
92
93
|
letschatty/models/company/assets/ai_agents_v2/context_item.py,sha256=9629_sJ4vjfF6--KR44E6lqVpc0uOGSlcorKS5ooSXU,438
|
|
93
94
|
letschatty/models/company/assets/ai_agents_v2/faq.py,sha256=qL-sr17ALvJ9PEzpaXpYL91-bQ9Np3C4jCYe-zfot6o,457
|
|
@@ -98,11 +99,10 @@ letschatty/models/company/assets/ai_agents_v2/n8n_agents_info.py,sha256=UNsYXznX
|
|
|
98
99
|
letschatty/models/company/assets/ai_agents_v2/n8n_schema_incoming_message_output.json,sha256=_NERM9gJ4Ry8NFVb8mHxRmdv0GfBrHdtmKxVJWmTUrY,2102
|
|
99
100
|
letschatty/models/company/assets/ai_agents_v2/n8n_schema_smart_follow_up_output.json,sha256=QAPRJXin-sE9NxQBHmmErSYHL6dQzmu4i0HEFSSXOi4,2350
|
|
100
101
|
letschatty/models/company/assets/ai_agents_v2/pre_qualify_config.py,sha256=o9kKywbnm1lEliTTV0G7oSCFroyqp_w0GkypL--sSk4,4181
|
|
101
|
-
letschatty/models/company/assets/ai_agents_v2/statuses.py,sha256=nhIGfeKednLNFZCIMavbZptT_9L-HDDVshAyCLdwPe8,1109
|
|
102
102
|
letschatty/models/company/assets/assignment/__init__.py,sha256=eWYZfaDQde5OJNIDed8D-LNRXOa5O_O4mGMToNFtaW8,239
|
|
103
103
|
letschatty/models/company/assets/assignment/assignment_assets.py,sha256=phIJqNh4UGTU-Hux_kaOYhJm2GCqv37AnCGePSDVKmM,2245
|
|
104
104
|
letschatty/models/company/assets/automation.py,sha256=RQFOvM-lipBdalfsRSZ8K5opytVgq_GS6YSR0Mz5Qs8,1639
|
|
105
|
-
letschatty/models/company/assets/chat_assets.py,sha256=
|
|
105
|
+
letschatty/models/company/assets/chat_assets.py,sha256=OV33LPOBOaK02Va0yH7rCH5ufmEyOploQPTEefjlsdk,7524
|
|
106
106
|
letschatty/models/company/assets/chatty_fast_answers/__init__.py,sha256=tbgWS0n1i0nVi9f79U44GPRnrW25NKcjl6Port92alk,48
|
|
107
107
|
letschatty/models/company/assets/chatty_fast_answers/chatty_fast_answer.py,sha256=PxW3eStHXo0BY7Z9hMDqBTHmgO7XcwtOvEpwXi-rA5g,1076
|
|
108
108
|
letschatty/models/company/assets/company_assets.py,sha256=9Or3hdUsWO0YCLqlwUb0Zcm_c5OerJqDx7rrrUK4_-E,800
|
|
@@ -116,7 +116,7 @@ letschatty/models/company/assets/launch/subscription.py,sha256=BnZuNK0jMOzn0s9zg
|
|
|
116
116
|
letschatty/models/company/assets/media/__init__.py,sha256=o22MHvoqGBUeG1P383KfBfsu-Fg0YdVh9FYeNB1j14s,38
|
|
117
117
|
letschatty/models/company/assets/media/file.py,sha256=MFhTNe8zsgjR1zMm2UIAznp4vtD4U12UMfu7mjFIGfE,1902
|
|
118
118
|
letschatty/models/company/assets/product.py,sha256=02eAZlE74nRzwOPAmbCHkQj7czVNgEtJQNzWxSBPP_w,2406
|
|
119
|
-
letschatty/models/company/assets/sale.py,sha256=
|
|
119
|
+
letschatty/models/company/assets/sale.py,sha256=OG9tGesyajvhXNyjSgOPc8EMcAVlqoaqAVflMKBzWa4,1078
|
|
120
120
|
letschatty/models/company/assets/tag.py,sha256=P1jRAvaebEtdAUs-07sRbCK25vmS6Q5NNtH7UX7bK-0,1008
|
|
121
121
|
letschatty/models/company/assets/users/agent_chats_snapshot.py,sha256=CTNRN8p9HE57bvxI8MbO2krg8KD3yIo3PmFjIV0iVWg,568
|
|
122
122
|
letschatty/models/company/assets/users/user.py,sha256=JWABDnvc9qhFnFcqBWSH2OAmVny37pUMBxo2CJGOMtA,9019
|
|
@@ -125,14 +125,9 @@ letschatty/models/company/assets/workflow_execution.py,sha256=CKlT9JTryKC8kNniRX
|
|
|
125
125
|
letschatty/models/company/company_chats_snapshot.py,sha256=Mg9Wmu3pfE-t5Sf57FCj-psvc5TkDkFfma-hdh6nE0Q,668
|
|
126
126
|
letschatty/models/company/company_messaging_settgins.py,sha256=7isXt7gmqw-FKSUSZwdctdXDFMfPi2OyPuWqB7WpC6c,957
|
|
127
127
|
letschatty/models/company/conversation_topic.py,sha256=__IinJ3YcUsiApF4Em5uAgd01ydRUrlCVxaat-pCCRg,1704
|
|
128
|
-
letschatty/models/company/empresa.py,sha256=
|
|
129
|
-
letschatty/models/company/form_field.py,sha256=
|
|
128
|
+
letschatty/models/company/empresa.py,sha256=2INPC8YSZJcT10Gny_zXV_PujeqUGccsZHV31ZSywyw,5961
|
|
129
|
+
letschatty/models/company/form_field.py,sha256=Twzpieo0yia_kJ8uEXJq7pX9drsKuBYEslAMa3iO0Mg,9680
|
|
130
130
|
letschatty/models/company/insight.py,sha256=B7BL07E4Z1b9aJHi3PXC1atln4-7fSo9JeqgQoeB_ao,7459
|
|
131
|
-
letschatty/models/company/integrations/product_sync_status.py,sha256=115zOvkaeXpHIUQftXe6dJsOUki3ru8hG3c75e2v4ag,814
|
|
132
|
-
letschatty/models/company/integrations/shopify/company_shopify_integration.py,sha256=PbxgIOG0Qp9Bz0WDrQffuSWrbEiV93JjX7PtHeYsqa0,2496
|
|
133
|
-
letschatty/models/company/integrations/shopify/shopify_product_sync_status.py,sha256=FhsmtFIf82pO9a1_OwV3MyKiJ6CGzlw-SCANZoV2M2g,588
|
|
134
|
-
letschatty/models/company/integrations/shopify/shopify_webhook_topics.py,sha256=HlSm0JHH6G3NPug8tbx9PQaC9yO3cwXJPTuU3qkWdDk,1263
|
|
135
|
-
letschatty/models/company/integrations/sync_status_enum.py,sha256=gh5kkKgLRqhygFFUPhITksp0Z-ImFEpGpzVABr7JiFM,154
|
|
136
131
|
letschatty/models/company/notifications/notification.py,sha256=wE7rIi21nZno6jjIxajMz4e7OJbzrDHjH1KdkNzJiF8,1907
|
|
137
132
|
letschatty/models/copilot/links.py,sha256=mcddNR6WdWOoOr3NgDl_FElxF15SiZZXw9wmIV08HRw,185
|
|
138
133
|
letschatty/models/data_base/__init__.py,sha256=cUTj-TBUYJn1iAofztCr3VUJFF9qg1vmaQlOuoKb1MM,45
|
|
@@ -235,7 +230,7 @@ letschatty/services/ai_agents/tool_descriptions/human_handover.json,sha256=-b3l_
|
|
|
235
230
|
letschatty/services/ai_agents/tool_descriptions.py,sha256=MMFhcgEl6Z7k8BUv3Yx1pCxHzIjc8HD93S0uM5OPj8Y,105
|
|
236
231
|
letschatty/services/ai_components_service.py,sha256=QUb-NdzHj4nPmnceVSQ9Pe3swFWUrkUFKzDZeuP0b5o,3762
|
|
237
232
|
letschatty/services/chat/chat_extractors.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
238
|
-
letschatty/services/chat/chat_service.py,sha256=
|
|
233
|
+
letschatty/services/chat/chat_service.py,sha256=h1AkzLDA7PSKXIuol18a5-lXq48ODfBPPLtHog0fzzE,47496
|
|
239
234
|
letschatty/services/chat/client_service.py,sha256=TtlG0GJDqx6YemfueiJzAtMNk_udnPhg0MJG0bKwnk8,1097
|
|
240
235
|
letschatty/services/chat/conversation_topics_service.py,sha256=jvIkpHTCz0JKXui3n_BRs6UQ-TB3x7DrQsnX2zUVFVw,2461
|
|
241
236
|
letschatty/services/chatty_assets/__init__.py,sha256=quOXMGvcDVRkbeaGfCQliFfZzis5m1wrfJkHqDkWRx8,132
|
|
@@ -250,7 +245,7 @@ letschatty/services/continuous_conversation_service/continuous_conversation_help
|
|
|
250
245
|
letschatty/services/events/events_manager.py,sha256=z2CAc-TqpXkyX9pPHImFXMIDRipLp7e-mN1gRvaM04E,67
|
|
251
246
|
letschatty/services/factories/__init__.py,sha256=cDAQ_0M5xKqZAui5ijHvbtHxn3jFFM4kBcXIXc_Bv38,161
|
|
252
247
|
letschatty/services/factories/analytics/contact_point_factory.py,sha256=5YXkoIwd43gtoYpFUACIXJGCJYyUEqS97XxpR_dBrt4,441
|
|
253
|
-
letschatty/services/factories/analytics/events_factory.py,sha256=
|
|
248
|
+
letschatty/services/factories/analytics/events_factory.py,sha256=25nrhNkDZat9g3JG73NTN22KNK0sM7B3zUmOzxz_n6A,36483
|
|
254
249
|
letschatty/services/factories/analytics/smart_messages/topics_factory.py,sha256=nJRlOtQFiosafXakZoJj8UCVoiNovjDiOoO52vIh4ok,682
|
|
255
250
|
letschatty/services/factories/analytics/sources/helpers.py,sha256=_2lsaTEhNFqr2ZyO8clMCD-t3MCPTQMyVoSUo5Fio_Y,868
|
|
256
251
|
letschatty/services/factories/analytics/sources/source_factory.py,sha256=kvpS9dLoCsIVnV_wMQzBpFXoN3AyjyphX-IFRg5sO5c,7877
|
|
@@ -279,8 +274,8 @@ letschatty/services/scheduled_messages_service/scheduled_messages_helpers.py,sha
|
|
|
279
274
|
letschatty/services/template_campaigns/template_campaign_service.py,sha256=jORgDncuXJ5ZV4fr_KsfKBHOz_lypIy6lcNMWMw8OPw,4038
|
|
280
275
|
letschatty/services/users/agent_service.py,sha256=hIkUUJ1SpkKbh5_uo4i2CeqGtuMTjU7tSV8k5J7WPG4,279
|
|
281
276
|
letschatty/services/users/user_factory.py,sha256=FCB9uiAfjMeYfh4kMdx5h8VDHJ8MCsD-uaxW3X3KaWM,6681
|
|
282
|
-
letschatty/services/validators/analytics_validator.py,sha256
|
|
283
|
-
letschatty-0.4.
|
|
284
|
-
letschatty-0.4.
|
|
285
|
-
letschatty-0.4.
|
|
286
|
-
letschatty-0.4.
|
|
277
|
+
letschatty/services/validators/analytics_validator.py,sha256=-QBR6XIqEv2qw3stcBQehkwui1EcfWUM6M9DRQODykY,6335
|
|
278
|
+
letschatty-0.4.345.dist-info/LICENSE,sha256=EClLu_bO2HBLDcThowIwfaIg5EOwIYhpRsBJjVEk92A,1197
|
|
279
|
+
letschatty-0.4.345.dist-info/METADATA,sha256=69SG0X--n-8x8UeYDl_s_pMMDarN9KqQmVTI3W0N9n8,3283
|
|
280
|
+
letschatty-0.4.345.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
|
281
|
+
letschatty-0.4.345.dist-info/RECORD,,
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
from enum import StrEnum
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class DataCollectionStatus(StrEnum):
|
|
5
|
-
"""
|
|
6
|
-
Status of data collection for the AI agent in this chat.
|
|
7
|
-
Only tracks field collection, not qualification status.
|
|
8
|
-
|
|
9
|
-
- COLLECTING: Still collecting data from the user
|
|
10
|
-
- MANDATORY_COMPLETED: All mandatory fields have been collected
|
|
11
|
-
- ALL_COMPLETED: All fields (mandatory + optional) have been collected
|
|
12
|
-
- CANCELLED: Data collection was cancelled
|
|
13
|
-
"""
|
|
14
|
-
COLLECTING = "collecting"
|
|
15
|
-
MANDATORY_COMPLETED = "mandatory_completed"
|
|
16
|
-
ALL_COMPLETED = "all_completed"
|
|
17
|
-
CANCELLED = "cancelled"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class PreQualifyStatus(StrEnum):
|
|
21
|
-
"""
|
|
22
|
-
Status of pre-qualification for the AI agent in this chat.
|
|
23
|
-
Separate from data collection - tracks qualification evaluation.
|
|
24
|
-
|
|
25
|
-
- PENDING: Waiting for data collection to complete
|
|
26
|
-
- EVALUATING: Data collected, evaluating acceptance criteria
|
|
27
|
-
- QUALIFIED: User met acceptance criteria
|
|
28
|
-
- UNQUALIFIED: User did NOT meet acceptance criteria
|
|
29
|
-
"""
|
|
30
|
-
PENDING = "pending"
|
|
31
|
-
EVALUATING = "evaluating"
|
|
32
|
-
QUALIFIED = "qualified"
|
|
33
|
-
UNQUALIFIED = "unqualified"
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
from typing import ClassVar, Optional
|
|
5
|
-
|
|
6
|
-
from pydantic import Field
|
|
7
|
-
|
|
8
|
-
from letschatty.models.company.integrations.sync_status_enum import SyncStatusEnum
|
|
9
|
-
from letschatty.models.base_models import CompanyAssetModel
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ProductSyncStatus(CompanyAssetModel):
|
|
13
|
-
"""Generic product sync status for any e-commerce integration."""
|
|
14
|
-
|
|
15
|
-
COLLECTION: ClassVar[str] = "product_sync_statuses"
|
|
16
|
-
|
|
17
|
-
integration_type: str = Field(
|
|
18
|
-
description="Integration type (shopify, tiendanube, etc.)"
|
|
19
|
-
)
|
|
20
|
-
status: SyncStatusEnum = Field(description="Current sync status")
|
|
21
|
-
|
|
22
|
-
products_created: int = Field(default=0)
|
|
23
|
-
products_updated: int = Field(default=0)
|
|
24
|
-
|
|
25
|
-
name: str = Field(default="")
|
|
26
|
-
|
|
27
|
-
finished_at: Optional[datetime] = Field(default=None)
|
|
28
|
-
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
2
|
-
from datetime import datetime
|
|
3
|
-
from zoneinfo import ZoneInfo
|
|
4
|
-
from pydantic import BaseModel, Field
|
|
5
|
-
|
|
6
|
-
class ShopifyWebhookSubscription(BaseModel):
|
|
7
|
-
"""Represents a single webhook subscription"""
|
|
8
|
-
topic: str = Field(description="Webhook topic (e.g., 'products/create')")
|
|
9
|
-
webhook_id: Optional[str] = Field(default=None, description="Shopify webhook ID")
|
|
10
|
-
subscribed_at: datetime = Field(default_factory=lambda: datetime.now(tz=ZoneInfo("UTC")))
|
|
11
|
-
is_active: bool = Field(default=True, description="Whether subscription is active")
|
|
12
|
-
|
|
13
|
-
class ShopifyIntegration(BaseModel):
|
|
14
|
-
"""Shopify integration for the company"""
|
|
15
|
-
shopify_store_url: str = Field(default="")
|
|
16
|
-
oauth_state: str = Field(default="")
|
|
17
|
-
oauth_state_at: datetime = Field(default_factory=lambda: datetime.now(tz=ZoneInfo("UTC")))
|
|
18
|
-
access_token: Optional[str] = Field(default=None)
|
|
19
|
-
connected_at: Optional[datetime] = Field(default=None)
|
|
20
|
-
scope: Optional[str] = Field(default=None)
|
|
21
|
-
|
|
22
|
-
# Webhook subscriptions
|
|
23
|
-
webhook_subscriptions: List[ShopifyWebhookSubscription] = Field(
|
|
24
|
-
default_factory=list,
|
|
25
|
-
description="List of active webhook subscriptions"
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
# Scheduled sync settings
|
|
29
|
-
product_sync_enabled: bool = Field(
|
|
30
|
-
default=False,
|
|
31
|
-
description="Whether scheduled product sync is enabled"
|
|
32
|
-
)
|
|
33
|
-
product_sync_interval_hours: int = Field(
|
|
34
|
-
default=24,
|
|
35
|
-
description="Interval in hours for scheduled product sync"
|
|
36
|
-
)
|
|
37
|
-
last_product_sync_at: Optional[datetime] = Field(
|
|
38
|
-
default=None,
|
|
39
|
-
description="Timestamp of last product sync"
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
@property
|
|
43
|
-
def is_connected(self) -> bool:
|
|
44
|
-
"""Check if the integration is fully connected"""
|
|
45
|
-
return bool(self.access_token and self.shopify_store_url)
|
|
46
|
-
|
|
47
|
-
def get_subscribed_topics(self) -> List[str]:
|
|
48
|
-
"""Get list of currently subscribed webhook topics"""
|
|
49
|
-
return [sub.topic for sub in self.webhook_subscriptions if sub.is_active]
|
|
50
|
-
|
|
51
|
-
def reset(self) -> None:
|
|
52
|
-
"""Reset integration to disconnected state"""
|
|
53
|
-
self.shopify_store_url = ""
|
|
54
|
-
self.oauth_state = ""
|
|
55
|
-
self.oauth_state_at = datetime.now(tz=ZoneInfo("UTC"))
|
|
56
|
-
self.access_token = None
|
|
57
|
-
self.connected_at = None
|
|
58
|
-
self.scope = None
|
|
59
|
-
self.webhook_subscriptions = []
|
|
60
|
-
self.product_sync_enabled = False
|
|
61
|
-
self.product_sync_interval_hours = 24
|
|
62
|
-
self.last_product_sync_at = None
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from pydantic import Field
|
|
2
|
-
|
|
3
|
-
from letschatty.models.company.integrations.product_sync_status import ProductSyncStatus
|
|
4
|
-
from letschatty.models.company.integrations.sync_status_enum import SyncStatusEnum
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
# Backwards-compatible alias (Shopify-specific name, generic enum)
|
|
8
|
-
ShopifyProductSyncStatusEnum = SyncStatusEnum
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class ShopifyProductSyncStatus(ProductSyncStatus):
|
|
12
|
-
"""Shopify-flavored wrapper for the generic ProductSyncStatus."""
|
|
13
|
-
|
|
14
|
-
integration_type: str = Field(
|
|
15
|
-
default="shopify",
|
|
16
|
-
frozen=True,
|
|
17
|
-
description="Integration type for this sync status"
|
|
18
|
-
)
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
from enum import Enum
|
|
2
|
-
|
|
3
|
-
class ShopifyWebhookTopic(str, Enum):
|
|
4
|
-
"""Shopify webhook topics for products and orders"""
|
|
5
|
-
|
|
6
|
-
# Product webhooks
|
|
7
|
-
PRODUCTS_CREATE = "products/create"
|
|
8
|
-
PRODUCTS_UPDATE = "products/update"
|
|
9
|
-
PRODUCTS_DELETE = "products/delete"
|
|
10
|
-
|
|
11
|
-
# Order webhooks
|
|
12
|
-
ORDERS_CREATE = "orders/create"
|
|
13
|
-
ORDERS_UPDATE = "orders/updated"
|
|
14
|
-
ORDERS_DELETE = "orders/delete"
|
|
15
|
-
ORDERS_FULFILLED = "orders/fulfilled"
|
|
16
|
-
ORDERS_PARTIALLY_FULFILLED = "orders/partially_fulfilled"
|
|
17
|
-
ORDERS_PAID = "orders/paid"
|
|
18
|
-
ORDERS_CANCELLED = "orders/cancelled"
|
|
19
|
-
|
|
20
|
-
@classmethod
|
|
21
|
-
def get_product_topics(cls) -> list[str]:
|
|
22
|
-
"""Get all product-related webhook topics"""
|
|
23
|
-
return [
|
|
24
|
-
cls.PRODUCTS_CREATE.value,
|
|
25
|
-
cls.PRODUCTS_UPDATE.value,
|
|
26
|
-
cls.PRODUCTS_DELETE.value,
|
|
27
|
-
]
|
|
28
|
-
|
|
29
|
-
@classmethod
|
|
30
|
-
def get_order_topics(cls) -> list[str]:
|
|
31
|
-
"""Get all order-related webhook topics"""
|
|
32
|
-
return [
|
|
33
|
-
cls.ORDERS_CREATE.value,
|
|
34
|
-
cls.ORDERS_UPDATE.value,
|
|
35
|
-
cls.ORDERS_DELETE.value,
|
|
36
|
-
cls.ORDERS_FULFILLED.value,
|
|
37
|
-
cls.ORDERS_PARTIALLY_FULFILLED.value,
|
|
38
|
-
cls.ORDERS_PAID.value,
|
|
39
|
-
cls.ORDERS_CANCELLED.value,
|
|
40
|
-
]
|
|
File without changes
|
|
File without changes
|