letschatty 0.4.338__py3-none-any.whl → 0.4.350__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/__init__.py +4 -4
- letschatty/models/ai_microservices/expected_output.py +29 -2
- letschatty/models/ai_microservices/lambda_events.py +155 -28
- letschatty/models/ai_microservices/lambda_invokation_types.py +4 -1
- letschatty/models/ai_microservices/n8n_ai_agents_payload.py +3 -1
- letschatty/models/analytics/events/__init__.py +3 -2
- letschatty/models/analytics/events/chat_based_events/ai_agent_execution_event.py +71 -0
- letschatty/models/analytics/events/chat_based_events/chat_funnel.py +13 -69
- letschatty/models/analytics/events/company_based_events/asset_events.py +2 -9
- letschatty/models/analytics/events/event_type_to_classes.py +3 -6
- letschatty/models/analytics/events/event_types.py +50 -9
- letschatty/models/chat/chat.py +0 -2
- letschatty/models/chat/chat_with_assets.py +1 -6
- letschatty/models/chat/client.py +2 -0
- letschatty/models/chat/continuous_conversation.py +1 -1
- letschatty/models/company/CRM/funnel.py +33 -365
- letschatty/models/company/__init__.py +1 -2
- letschatty/models/company/assets/ai_agents_v2/ai_agents_decision_output.py +1 -1
- letschatty/models/company/assets/ai_agents_v2/chain_of_thought_in_chat.py +5 -3
- letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_in_chat.py +4 -0
- letschatty/models/company/assets/ai_agents_v2/chatty_ai_mode.py +2 -2
- letschatty/models/company/assets/ai_agents_v2/get_chat_with_prompt_response.py +1 -0
- letschatty/models/company/assets/ai_agents_v2/pre_qualify_config.py +29 -1
- letschatty/models/company/assets/automation.py +19 -10
- letschatty/models/company/assets/company_assets.py +0 -2
- letschatty/models/data_base/collection_interface.py +101 -29
- letschatty/models/data_base/mongo_connection.py +92 -9
- letschatty/models/messages/chatty_messages/schema/chatty_content/content_document.py +2 -4
- letschatty/models/messages/chatty_messages/schema/chatty_content/content_media.py +3 -4
- letschatty/models/utils/custom_exceptions/custom_exceptions.py +14 -1
- letschatty/services/ai_agents/smart_follow_up_context_builder_v2.py +5 -2
- letschatty/services/chatty_assets/__init__.py +12 -0
- letschatty/services/chatty_assets/asset_service.py +190 -13
- letschatty/services/chatty_assets/assets_collections.py +137 -0
- letschatty/services/chatty_assets/base_container.py +3 -2
- letschatty/services/chatty_assets/base_container_with_collection.py +35 -26
- letschatty/services/chatty_assets/collections/__init__.py +38 -0
- letschatty/services/chatty_assets/collections/ai_agent_collection.py +19 -0
- letschatty/services/chatty_assets/collections/ai_agent_in_chat_collection.py +32 -0
- letschatty/services/chatty_assets/collections/ai_component_collection.py +21 -0
- letschatty/services/chatty_assets/collections/chain_of_thought_collection.py +30 -0
- letschatty/services/chatty_assets/collections/chat_collection.py +21 -0
- letschatty/services/chatty_assets/collections/contact_point_collection.py +21 -0
- letschatty/services/chatty_assets/collections/fast_answer_collection.py +21 -0
- letschatty/services/chatty_assets/collections/filter_criteria_collection.py +18 -0
- letschatty/services/chatty_assets/collections/flow_collection.py +20 -0
- letschatty/services/chatty_assets/collections/product_collection.py +20 -0
- letschatty/services/chatty_assets/collections/sale_collection.py +20 -0
- letschatty/services/chatty_assets/collections/source_collection.py +21 -0
- letschatty/services/chatty_assets/collections/tag_collection.py +19 -0
- letschatty/services/chatty_assets/collections/topic_collection.py +21 -0
- letschatty/services/chatty_assets/collections/user_collection.py +20 -0
- letschatty/services/chatty_assets/example_usage.py +44 -0
- letschatty/services/chatty_assets/services/__init__.py +37 -0
- letschatty/services/chatty_assets/services/ai_agent_in_chat_service.py +73 -0
- letschatty/services/chatty_assets/services/ai_agent_service.py +23 -0
- letschatty/services/chatty_assets/services/chain_of_thought_service.py +70 -0
- letschatty/services/chatty_assets/services/chat_service.py +25 -0
- letschatty/services/chatty_assets/services/contact_point_service.py +29 -0
- letschatty/services/chatty_assets/services/fast_answer_service.py +32 -0
- letschatty/services/chatty_assets/services/filter_criteria_service.py +30 -0
- letschatty/services/chatty_assets/services/flow_service.py +25 -0
- letschatty/services/chatty_assets/services/product_service.py +30 -0
- letschatty/services/chatty_assets/services/sale_service.py +25 -0
- letschatty/services/chatty_assets/services/source_service.py +28 -0
- letschatty/services/chatty_assets/services/tag_service.py +32 -0
- letschatty/services/chatty_assets/services/topic_service.py +31 -0
- letschatty/services/chatty_assets/services/user_service.py +32 -0
- letschatty/services/continuous_conversation_service/continuous_conversation_helper.py +11 -0
- letschatty/services/events/__init__.py +6 -0
- letschatty/services/events/events_manager.py +218 -1
- letschatty/services/factories/analytics/ai_agent_event_factory.py +161 -0
- letschatty/services/factories/analytics/events_factory.py +66 -6
- letschatty/services/factories/lambda_ai_orchestrartor/lambda_events_factory.py +46 -8
- letschatty/services/messages_helpers/get_caption_or_body_or_preview.py +6 -4
- {letschatty-0.4.338.dist-info → letschatty-0.4.350.dist-info}/METADATA +1 -1
- {letschatty-0.4.338.dist-info → letschatty-0.4.350.dist-info}/RECORD +79 -44
- letschatty/models/company/company_shopify_integration.py +0 -10
- {letschatty-0.4.338.dist-info → letschatty-0.4.350.dist-info}/LICENSE +0 -0
- {letschatty-0.4.338.dist-info → letschatty-0.4.350.dist-info}/WHEEL +0 -0
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import TypeVar, Generic, Type, Callable, Protocol, Optional
|
|
2
|
+
from typing import TypeVar, Generic, Type, Callable, Protocol, Optional, ClassVar, TYPE_CHECKING, List
|
|
3
|
+
|
|
4
|
+
from bson import ObjectId
|
|
5
|
+
from letschatty.models.utils.types import StrObjectId
|
|
3
6
|
from .base_container_with_collection import ChattyAssetCollectionInterface, ChattyAssetContainerWithCollection, CacheConfig
|
|
4
7
|
from ...models.base_models import ChattyAssetModel
|
|
5
8
|
from ...models.base_models.chatty_asset_model import ChattyAssetPreview
|
|
6
9
|
from ...models.data_base.mongo_connection import MongoConnection
|
|
7
10
|
import logging
|
|
8
11
|
import os
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ...models.analytics.events.base import EventType
|
|
15
|
+
from ...models.company.empresa import EmpresaModel
|
|
16
|
+
from ...models.execution.execution import ExecutionContext
|
|
17
|
+
from ...models.company.assets.company_assets import CompanyAssetType
|
|
18
|
+
from ...models.utils.types.deletion_type import DeletionType
|
|
19
|
+
|
|
9
20
|
logger = logging.getLogger("AssetService")
|
|
10
21
|
|
|
11
22
|
# Protocol for assets that specify their preview type
|
|
@@ -44,17 +55,75 @@ class AssetCollection(Generic[T, P], ChattyAssetCollectionInterface[T, P]):
|
|
|
44
55
|
raise ValueError(f"Data must be a dictionary, got {type(data)}: {data}")
|
|
45
56
|
return self._create_instance_method(data)
|
|
46
57
|
|
|
58
|
+
|
|
47
59
|
class AssetService(Generic[T, P], ChattyAssetContainerWithCollection[T, P]):
|
|
48
|
-
"""
|
|
60
|
+
"""
|
|
61
|
+
Generic service for handling CRUD operations for any Chatty asset.
|
|
62
|
+
|
|
63
|
+
Supports optional automatic event handling for API implementations.
|
|
64
|
+
Set these class attributes to enable events:
|
|
65
|
+
- asset_type_enum: CompanyAssetType (e.g., CompanyAssetType.PRODUCTS)
|
|
66
|
+
- event_type_created: EventType (e.g., EventType.PRODUCT_CREATED)
|
|
67
|
+
- event_type_updated: EventType (e.g., EventType.PRODUCT_UPDATED)
|
|
68
|
+
- event_type_deleted: EventType (e.g., EventType.PRODUCT_DELETED)
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
# Optional: Set these in subclasses to enable automatic event handling
|
|
72
|
+
asset_type_enum: ClassVar[Optional['CompanyAssetType']] = None
|
|
73
|
+
event_type_created: ClassVar[Optional['EventType']] = None
|
|
74
|
+
event_type_updated: ClassVar[Optional['EventType']] = None
|
|
75
|
+
event_type_deleted: ClassVar[Optional['EventType']] = None
|
|
76
|
+
|
|
77
|
+
collection: AssetCollection[T, P] # Type annotation for better type checking
|
|
49
78
|
|
|
50
79
|
def __init__(self,
|
|
51
|
-
|
|
52
|
-
asset_type: Type[T],
|
|
53
|
-
connection: MongoConnection,
|
|
54
|
-
create_instance_method: Callable[[dict], T],
|
|
55
|
-
preview_type: Optional[Type[P]] = None,
|
|
80
|
+
collection: AssetCollection[T, P],
|
|
56
81
|
cache_config: CacheConfig = CacheConfig.default()):
|
|
57
|
-
|
|
82
|
+
"""
|
|
83
|
+
Initialize AssetService with a pre-configured collection.
|
|
84
|
+
|
|
85
|
+
The item_type and preview_type are automatically extracted from the collection,
|
|
86
|
+
eliminating redundancy and simplifying the API.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
collection: Pre-configured AssetCollection subclass
|
|
90
|
+
cache_config: Cache configuration
|
|
91
|
+
"""
|
|
92
|
+
logger.debug(f"AssetService {self.__class__.__name__} initializing with collection")
|
|
93
|
+
super().__init__(
|
|
94
|
+
item_type=collection.type,
|
|
95
|
+
preview_type=collection.preview_type,
|
|
96
|
+
collection=collection,
|
|
97
|
+
cache_config=cache_config,
|
|
98
|
+
)
|
|
99
|
+
logger.debug(f"AssetService {self.__class__.__name__} initialized")
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def from_config(cls,
|
|
103
|
+
collection_name: str,
|
|
104
|
+
asset_type: Type[T],
|
|
105
|
+
connection: MongoConnection,
|
|
106
|
+
create_instance_method: Callable[[dict], T],
|
|
107
|
+
preview_type: Optional[Type[P]] = None,
|
|
108
|
+
cache_config: CacheConfig = CacheConfig.default()) -> 'AssetService[T, P]':
|
|
109
|
+
"""
|
|
110
|
+
Create an AssetService using the legacy configuration pattern.
|
|
111
|
+
|
|
112
|
+
This class method is provided for backward compatibility.
|
|
113
|
+
New code should use pre-configured AssetCollection subclasses.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
collection_name: MongoDB collection name
|
|
117
|
+
asset_type: The asset model type
|
|
118
|
+
connection: MongoDB connection
|
|
119
|
+
create_instance_method: Factory method to create asset instances
|
|
120
|
+
preview_type: Optional preview type
|
|
121
|
+
cache_config: Cache configuration
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
AssetService instance
|
|
125
|
+
"""
|
|
126
|
+
logger.debug(f"AssetService creating from config for {collection_name}")
|
|
58
127
|
asset_collection = AssetCollection(
|
|
59
128
|
collection=collection_name,
|
|
60
129
|
asset_type=asset_type,
|
|
@@ -62,13 +131,102 @@ class AssetService(Generic[T, P], ChattyAssetContainerWithCollection[T, P]):
|
|
|
62
131
|
create_instance_method=create_instance_method,
|
|
63
132
|
preview_type=preview_type
|
|
64
133
|
)
|
|
65
|
-
|
|
66
|
-
item_type=asset_type,
|
|
67
|
-
preview_type=preview_type,
|
|
134
|
+
return cls(
|
|
68
135
|
collection=asset_collection,
|
|
69
|
-
cache_config=cache_config
|
|
136
|
+
cache_config=cache_config
|
|
70
137
|
)
|
|
71
|
-
|
|
138
|
+
|
|
139
|
+
def _should_handle_events(self) -> bool:
|
|
140
|
+
"""Check if this service should handle events automatically"""
|
|
141
|
+
return (self.asset_type_enum is not None and
|
|
142
|
+
self.event_type_created is not None and
|
|
143
|
+
self.event_type_updated is not None and
|
|
144
|
+
self.event_type_deleted is not None)
|
|
145
|
+
|
|
146
|
+
def _queue_event(self, item: T, event_type: 'EventType', execution_context: 'ExecutionContext', company_info: 'EmpresaModel'):
|
|
147
|
+
"""Queue an event for this asset if event handling is enabled"""
|
|
148
|
+
if not self._should_handle_events() or not self.asset_type_enum:
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
from ...services.factories.analytics.events_factory import EventFactory
|
|
153
|
+
from ...services.events import events_manager
|
|
154
|
+
|
|
155
|
+
# Type guard - company_id should exist on ChattyAssetModel
|
|
156
|
+
if not hasattr(item, 'company_id'):
|
|
157
|
+
logger.warning(f"Asset {type(item).__name__} missing company_id, skipping event")
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
events = EventFactory.asset_events(
|
|
161
|
+
company_id=item.company_id, # type: ignore[attr-defined]
|
|
162
|
+
executor_id=execution_context.executor.id,
|
|
163
|
+
asset=item,
|
|
164
|
+
asset_type=self.asset_type_enum,
|
|
165
|
+
event_type=event_type,
|
|
166
|
+
time=execution_context.time,
|
|
167
|
+
trace_id=execution_context.trace_id,
|
|
168
|
+
executor_type=execution_context.executor.type,
|
|
169
|
+
company_info=company_info
|
|
170
|
+
)
|
|
171
|
+
events_manager.queue_events(events)
|
|
172
|
+
except ImportError:
|
|
173
|
+
# Events not available (microservice context) - skip
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
# All methods are now async-only for better performance
|
|
177
|
+
async def insert(self, item: T, execution_context: 'ExecutionContext', company_info: Optional['EmpresaModel'] = None) -> T:
|
|
178
|
+
"""Insert with automatic event handling if configured"""
|
|
179
|
+
result = await super().insert(item, execution_context)
|
|
180
|
+
if company_info and self._should_handle_events() and self.event_type_created:
|
|
181
|
+
self._queue_event(result, self.event_type_created, execution_context, company_info)
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
async def update(self, id: str, new_item: T, execution_context: 'ExecutionContext', company_info: Optional['EmpresaModel'] = None) -> T:
|
|
185
|
+
"""Update with automatic event handling if configured"""
|
|
186
|
+
result = await super().update(id, new_item, execution_context)
|
|
187
|
+
if company_info and self._should_handle_events() and self.event_type_updated:
|
|
188
|
+
self._queue_event(result, self.event_type_updated, execution_context, company_info)
|
|
189
|
+
return result
|
|
190
|
+
|
|
191
|
+
async def delete(self, id: str, execution_context: 'ExecutionContext', company_info: Optional['EmpresaModel'] = None, deletion_type: Optional['DeletionType'] = None) -> T:
|
|
192
|
+
"""Delete with automatic event handling if configured"""
|
|
193
|
+
from ...models.utils.types.deletion_type import DeletionType as DT
|
|
194
|
+
result = await super().delete(id, execution_context, deletion_type or DT.LOGICAL)
|
|
195
|
+
if company_info and self._should_handle_events() and self.event_type_deleted:
|
|
196
|
+
self._queue_event(result, self.event_type_deleted, execution_context, company_info)
|
|
197
|
+
return result
|
|
198
|
+
|
|
199
|
+
async def restore(self, id: str, execution_context: 'ExecutionContext', company_info: Optional['EmpresaModel'] = None) -> T:
|
|
200
|
+
"""Restore with automatic event handling if configured"""
|
|
201
|
+
result = await super().restore(id, execution_context)
|
|
202
|
+
if company_info and self._should_handle_events() and self.event_type_updated:
|
|
203
|
+
self._queue_event(result, self.event_type_updated, execution_context, company_info)
|
|
204
|
+
return result
|
|
205
|
+
|
|
206
|
+
# Generic convenience methods
|
|
207
|
+
async def create_asset(self, data: dict, execution_context: 'ExecutionContext', company_info: 'EmpresaModel') -> T:
|
|
208
|
+
"""
|
|
209
|
+
Generic create method - creates instance from dict and inserts with events.
|
|
210
|
+
Can be called as create_asset or aliased to create_product/create_tag/etc.
|
|
211
|
+
"""
|
|
212
|
+
data["company_id"] = execution_context.company_id
|
|
213
|
+
item = self.collection.create_instance(data)
|
|
214
|
+
return await self.insert(item, execution_context, company_info)
|
|
215
|
+
|
|
216
|
+
async def update_asset(self, id: str, data: dict, execution_context: 'ExecutionContext', company_info: 'EmpresaModel') -> T:
|
|
217
|
+
"""
|
|
218
|
+
Generic update method - creates instance from dict and updates with events.
|
|
219
|
+
Can be called as update_asset or aliased to update_product/update_tag/etc.
|
|
220
|
+
"""
|
|
221
|
+
new_item = self.collection.create_instance(data)
|
|
222
|
+
return await self.update(id, new_item, execution_context, company_info)
|
|
223
|
+
|
|
224
|
+
async def delete_asset(self, id: str, execution_context: 'ExecutionContext', company_info: 'EmpresaModel') -> T:
|
|
225
|
+
"""
|
|
226
|
+
Generic delete method - deletes with events.
|
|
227
|
+
Can be called as delete_asset or aliased to delete_product/delete_tag/etc.
|
|
228
|
+
"""
|
|
229
|
+
return await self.delete(id, execution_context, company_info)
|
|
72
230
|
|
|
73
231
|
def get_preview_type(self) -> Type[P]:
|
|
74
232
|
"""Get the preview type from the asset class if it has one"""
|
|
@@ -81,3 +239,22 @@ class AssetService(Generic[T, P], ChattyAssetContainerWithCollection[T, P]):
|
|
|
81
239
|
preview_type = self.get_preview_type()
|
|
82
240
|
return super().get_preview_by_id(id, company_id, preview_type)
|
|
83
241
|
|
|
242
|
+
# Additional async read methods (passthrough to base class)
|
|
243
|
+
async def get_by_id(self, id: str) -> T:
|
|
244
|
+
"""Get by ID"""
|
|
245
|
+
return await super().get_by_id(id)
|
|
246
|
+
|
|
247
|
+
async def get_all(self, company_id: str) -> List[T]:
|
|
248
|
+
"""Get all for company"""
|
|
249
|
+
return await super().get_all(company_id)
|
|
250
|
+
|
|
251
|
+
async def get_by_query(self, query: dict, company_id: Optional[str]) -> List[T]:
|
|
252
|
+
"""Get by query"""
|
|
253
|
+
return await super().get_by_query(query, company_id)
|
|
254
|
+
|
|
255
|
+
async def get_item_dumped(self, id: str) -> dict:
|
|
256
|
+
"""Get item by ID and return as JSON serialized dict for frontend"""
|
|
257
|
+
from ...models.utils.types.serializer_type import SerializerType
|
|
258
|
+
item = await self.get_by_id(id)
|
|
259
|
+
return item.model_dump_json(serializer=SerializerType.FRONTEND)
|
|
260
|
+
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Read-only Assets Collections Container
|
|
3
|
+
|
|
4
|
+
This module provides a singleton container class that gives microservices
|
|
5
|
+
read-only access to asset data using pre-configured AssetCollection subclasses.
|
|
6
|
+
Perfect for services that only need to read asset data without CRUD operations.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
# Import base components
|
|
13
|
+
from ...models.data_base.mongo_connection import MongoConnection
|
|
14
|
+
from ...models.base_models.singleton import SingletonMeta
|
|
15
|
+
|
|
16
|
+
# Import pre-configured collection subclasses
|
|
17
|
+
from .collections import (
|
|
18
|
+
ProductCollection,
|
|
19
|
+
TagCollection,
|
|
20
|
+
UserCollection,
|
|
21
|
+
ChatCollection,
|
|
22
|
+
SourceCollection,
|
|
23
|
+
FlowCollection,
|
|
24
|
+
SaleCollection,
|
|
25
|
+
ContactPointCollection,
|
|
26
|
+
AiAgentCollection,
|
|
27
|
+
FilterCriteriaCollection,
|
|
28
|
+
AiComponentCollection
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Import asset models for type hints
|
|
32
|
+
from ...models.company.assets.product import Product
|
|
33
|
+
from ...models.company.assets.tag import Tag
|
|
34
|
+
from ...models.company.assets.users.user import User
|
|
35
|
+
from ...models.chat.chat import Chat
|
|
36
|
+
from ...models.analytics.sources import SourceBase
|
|
37
|
+
from ...models.company.assets.flow import FlowPreview
|
|
38
|
+
from ...models.company.assets.sale import Sale
|
|
39
|
+
from ...models.company.assets.contact_point import ContactPoint
|
|
40
|
+
from ...models.company.assets.ai_agents_v2.chatty_ai_agent import ChattyAIAgent
|
|
41
|
+
from ...models.company.assets.filter_criteria import FilterCriteria
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
logger = logging.getLogger("AssetsCollections")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AssetsCollections(metaclass=SingletonMeta):
|
|
50
|
+
"""
|
|
51
|
+
Read-only singleton container for accessing asset collections across microservices.
|
|
52
|
+
|
|
53
|
+
This class provides simple read access to various asset types without the overhead
|
|
54
|
+
of the full AssetService (no caching, no events, no write operations).
|
|
55
|
+
|
|
56
|
+
Usage:
|
|
57
|
+
assets = AssetsCollections(connection)
|
|
58
|
+
product = assets.get_product_by_id(product_id)
|
|
59
|
+
tag = assets.get_tag_by_id(tag_id)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, connection: MongoConnection):
|
|
63
|
+
"""
|
|
64
|
+
Initialize all asset collections using pre-configured collection subclasses.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
connection: MongoConnection instance to use for database access
|
|
68
|
+
"""
|
|
69
|
+
logger.debug("Initializing AssetsCollections")
|
|
70
|
+
|
|
71
|
+
# Initialize all collections using pre-configured subclasses
|
|
72
|
+
# Each collection subclass already knows its collection name, asset type,
|
|
73
|
+
# preview type, and create_instance_method
|
|
74
|
+
self.products = ProductCollection(connection)
|
|
75
|
+
self.tags = TagCollection(connection)
|
|
76
|
+
self.users = UserCollection(connection)
|
|
77
|
+
self.chats = ChatCollection(connection)
|
|
78
|
+
self.sources = SourceCollection(connection)
|
|
79
|
+
self.flows = FlowCollection(connection)
|
|
80
|
+
self.sales = SaleCollection(connection)
|
|
81
|
+
self.contact_points = ContactPointCollection(connection)
|
|
82
|
+
self.ai_agents = AiAgentCollection(connection)
|
|
83
|
+
self.filter_criterias = FilterCriteriaCollection(connection)
|
|
84
|
+
self.ai_components = AiComponentCollection(connection)
|
|
85
|
+
|
|
86
|
+
logger.debug("AssetsCollections initialized successfully")
|
|
87
|
+
|
|
88
|
+
# Convenience getter methods for easy access
|
|
89
|
+
|
|
90
|
+
async def get_product_by_id(self, id: str) -> Product:
|
|
91
|
+
"""Get a product by ID."""
|
|
92
|
+
return await self.products.get_by_id(id)
|
|
93
|
+
|
|
94
|
+
async def get_tag_by_id(self, id: str) -> Tag:
|
|
95
|
+
"""Get a tag by ID."""
|
|
96
|
+
return await self.tags.get_by_id(id)
|
|
97
|
+
|
|
98
|
+
async def get_user_by_id(self, id: str) -> User:
|
|
99
|
+
"""Get a user by ID."""
|
|
100
|
+
return await self.users.get_by_id(id)
|
|
101
|
+
|
|
102
|
+
async def get_chat_by_id(self, id: str) -> Chat:
|
|
103
|
+
"""Get a chat by ID."""
|
|
104
|
+
return await self.chats.get_by_id(id)
|
|
105
|
+
|
|
106
|
+
async def get_source_by_id(self, id: str) -> SourceBase:
|
|
107
|
+
"""Get a source by ID."""
|
|
108
|
+
return await self.sources.get_by_id(id)
|
|
109
|
+
|
|
110
|
+
async def get_flow_by_id(self, id: str) -> FlowPreview:
|
|
111
|
+
"""Get a flow by ID."""
|
|
112
|
+
return await self.flows.get_by_id(id)
|
|
113
|
+
|
|
114
|
+
async def get_sale_by_id(self, id: str) -> Sale:
|
|
115
|
+
"""Get a sale by ID."""
|
|
116
|
+
return await self.sales.get_by_id(id)
|
|
117
|
+
|
|
118
|
+
async def get_contact_point_by_id(self, id: str) -> ContactPoint:
|
|
119
|
+
"""Get a contact point by ID."""
|
|
120
|
+
return await self.contact_points.get_by_id(id)
|
|
121
|
+
|
|
122
|
+
async def get_ai_agent_by_id(self, id: str) -> ChattyAIAgent:
|
|
123
|
+
"""Get an AI agent by ID."""
|
|
124
|
+
return await self.ai_agents.get_by_id(id)
|
|
125
|
+
|
|
126
|
+
async def get_filter_criteria_by_id(self, id: str) -> FilterCriteria:
|
|
127
|
+
"""Get a filter criteria by ID."""
|
|
128
|
+
return await self.filter_criterias.get_by_id(id)
|
|
129
|
+
|
|
130
|
+
async def get_filter_criterias_by_ids(self, ids: list[str]) -> list[FilterCriteria]:
|
|
131
|
+
"""Get multiple filter criterias by their IDs in a single query."""
|
|
132
|
+
return await self.filter_criterias.get_by_ids(ids=ids)
|
|
133
|
+
|
|
134
|
+
async def get_ai_components_by_ids(self, ids: list[str]) -> list[Any]:
|
|
135
|
+
"""Get multiple AI components by their IDs in a single query."""
|
|
136
|
+
return await self.ai_components.get_by_ids(ids=ids)
|
|
137
|
+
|
|
@@ -123,8 +123,9 @@ class ChattyAssetBaseContainer(Generic[T, P], ABC):
|
|
|
123
123
|
else:
|
|
124
124
|
return list(self.items.values())
|
|
125
125
|
|
|
126
|
-
def get_all_dict_id_item(self, company_id:Optional[StrObjectId]) -> Dict[StrObjectId, T]:
|
|
127
|
-
|
|
126
|
+
async def get_all_dict_id_item(self, company_id:Optional[StrObjectId]) -> Dict[StrObjectId, T]:
|
|
127
|
+
items = await self.get_all(company_id)
|
|
128
|
+
return {item.id: item for item in items}
|
|
128
129
|
|
|
129
130
|
def get_all_previews(self, company_id:Optional[StrObjectId]) -> List[P]:
|
|
130
131
|
logger.debug(f"Getting all previews for {self.__class__.__name__}")
|
|
@@ -64,7 +64,8 @@ class ChattyAssetContainerWithCollection(ChattyAssetBaseContainer[T, P], ABC):
|
|
|
64
64
|
self.update_previews_thread()
|
|
65
65
|
self.load_from_db_thread(company_id=None)
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
# All methods are now async-only for better performance
|
|
68
|
+
async def insert(self, item: T, execution_context: ExecutionContext) -> T:
|
|
68
69
|
"""
|
|
69
70
|
Add an item to the container and insert it into the database collection.
|
|
70
71
|
|
|
@@ -77,12 +78,12 @@ class ChattyAssetContainerWithCollection(ChattyAssetBaseContainer[T, P], ABC):
|
|
|
77
78
|
"""
|
|
78
79
|
logger.debug(f"{self.__class__.__name__} inserting item {item}")
|
|
79
80
|
inserted_item = super().insert(item)
|
|
80
|
-
self.collection.insert(inserted_item)
|
|
81
|
+
await self.collection.insert(inserted_item)
|
|
81
82
|
execution_context.set_event_time(inserted_item.created_at)
|
|
82
83
|
self.update_previews_thread()
|
|
83
84
|
return inserted_item
|
|
84
85
|
|
|
85
|
-
def update(self, id: str, new_item: T, execution_context: ExecutionContext) -> T:
|
|
86
|
+
async def update(self, id: str, new_item: T, execution_context: ExecutionContext) -> T:
|
|
86
87
|
"""
|
|
87
88
|
Update an item in the container and in the database collection.
|
|
88
89
|
|
|
@@ -105,18 +106,18 @@ class ChattyAssetContainerWithCollection(ChattyAssetBaseContainer[T, P], ABC):
|
|
|
105
106
|
if id != updated_item.id:
|
|
106
107
|
logger.error(f"Item id {id} does not match updated item id {updated_item.id}")
|
|
107
108
|
raise ValueError(f"Item id {id} does not match updated item id {updated_item.id}")
|
|
108
|
-
self.collection.update(updated_item)
|
|
109
|
+
await self.collection.update(updated_item)
|
|
109
110
|
execution_context.set_event_time(updated_item.updated_at)
|
|
110
111
|
self.update_preview(updated_item)
|
|
111
112
|
self.update_previews_thread()
|
|
112
113
|
return updated_item
|
|
113
114
|
|
|
114
115
|
except NotFoundError as e:
|
|
115
|
-
outdated_item = self.collection.get_by_id(id)
|
|
116
|
+
outdated_item = await self.collection.get_by_id(id)
|
|
116
117
|
if outdated_item:
|
|
117
118
|
updated_item = outdated_item.update(new_item)
|
|
118
119
|
self.items[id] = updated_item
|
|
119
|
-
self.collection.update(updated_item)
|
|
120
|
+
await self.collection.update(updated_item)
|
|
120
121
|
execution_context.set_event_time(updated_item.updated_at)
|
|
121
122
|
self.update_previews_thread()
|
|
122
123
|
return updated_item
|
|
@@ -125,7 +126,7 @@ class ChattyAssetContainerWithCollection(ChattyAssetBaseContainer[T, P], ABC):
|
|
|
125
126
|
f"Item with id {id} not found in {self.__class__.__name__} nor in collection DB"
|
|
126
127
|
)
|
|
127
128
|
|
|
128
|
-
def delete(self, id: str, execution_context: ExecutionContext,deletion_type : DeletionType = DeletionType.LOGICAL) -> T:
|
|
129
|
+
async def delete(self, id: str, execution_context: ExecutionContext,deletion_type : DeletionType = DeletionType.LOGICAL) -> T:
|
|
129
130
|
"""
|
|
130
131
|
Delete an item from the container and the collection.
|
|
131
132
|
|
|
@@ -142,16 +143,16 @@ class ChattyAssetContainerWithCollection(ChattyAssetBaseContainer[T, P], ABC):
|
|
|
142
143
|
deleted_item = super().delete(id)
|
|
143
144
|
self.delete_preview(id)
|
|
144
145
|
execution_context.set_event_time(datetime.now(ZoneInfo("UTC")))
|
|
145
|
-
self.collection.delete(id, deletion_type)
|
|
146
|
+
await self.collection.delete(id, deletion_type)
|
|
146
147
|
return deleted_item
|
|
147
148
|
except NotFoundError as e:
|
|
148
|
-
self.collection.delete(id, deletion_type)
|
|
149
|
+
await self.collection.delete(id, deletion_type)
|
|
149
150
|
self.delete_preview(id)
|
|
150
151
|
self.update_previews_thread()
|
|
151
152
|
execution_context.set_event_time(datetime.now(ZoneInfo("UTC")))
|
|
152
|
-
return self.collection.get_by_id(id)
|
|
153
|
+
return await self.collection.get_by_id(id)
|
|
153
154
|
|
|
154
|
-
def get_by_id(self, id: str) -> T:
|
|
155
|
+
async def get_by_id(self, id: str) -> T:
|
|
155
156
|
"""
|
|
156
157
|
Get an item from the container.
|
|
157
158
|
|
|
@@ -174,7 +175,7 @@ class ChattyAssetContainerWithCollection(ChattyAssetBaseContainer[T, P], ABC):
|
|
|
174
175
|
# #if they are supposed to be in memory, we raise an error since it shouldn't be in the collection DB
|
|
175
176
|
# raise NotFoundError(f"Item with id {id} not found in {self.__class__.__name__} nor in collection DB")
|
|
176
177
|
logger.debug(f"{self.__class__.__name__} getting item {id} not found in container, trying to get from collection")
|
|
177
|
-
item = self.collection.get_by_id(id)
|
|
178
|
+
item = await self.collection.get_by_id(id)
|
|
178
179
|
if item:
|
|
179
180
|
if item.deleted_at is not None:
|
|
180
181
|
return item
|
|
@@ -239,7 +240,7 @@ class ChattyAssetContainerWithCollection(ChattyAssetBaseContainer[T, P], ABC):
|
|
|
239
240
|
logger.debug(f"Clearing previews cache of {self.__class__.__name__}")
|
|
240
241
|
self.set_preview_items([])
|
|
241
242
|
|
|
242
|
-
def get_all(self, company_id: Optional[StrObjectId]) -> List[T]:
|
|
243
|
+
async def get_all(self, company_id: Optional[StrObjectId]) -> List[T]:
|
|
243
244
|
# Get items from memory
|
|
244
245
|
logger.debug(f"{self.__class__.__name__} getting all items from memory and collection")
|
|
245
246
|
memory_items = super().get_all(company_id=company_id)
|
|
@@ -247,7 +248,7 @@ class ChattyAssetContainerWithCollection(ChattyAssetBaseContainer[T, P], ABC):
|
|
|
247
248
|
memory_ids = [ObjectId(item.id) for item in memory_items]
|
|
248
249
|
# Build the query for collection items
|
|
249
250
|
query = {"deleted_at": None, "_id": {"$nin": memory_ids}}
|
|
250
|
-
collection_items = self.collection.get_docs(query=query, company_id=company_id)
|
|
251
|
+
collection_items = await self.collection.get_docs(query=query, company_id=company_id)
|
|
251
252
|
all_items = memory_items + collection_items
|
|
252
253
|
return sorted(all_items, key=lambda x: x.created_at, reverse=True)
|
|
253
254
|
|
|
@@ -261,6 +262,8 @@ class ChattyAssetContainerWithCollection(ChattyAssetBaseContainer[T, P], ABC):
|
|
|
261
262
|
def update_previews_thread(self):
|
|
262
263
|
"""We start a thread to update the previews cache so it doesn't block the main thread"""
|
|
263
264
|
# self.update_previews_cache()
|
|
265
|
+
if not self.cache_config.keep_previews_always_in_memory:
|
|
266
|
+
return
|
|
264
267
|
thread = threading.Thread(target=self.update_previews_cache)
|
|
265
268
|
thread.start()
|
|
266
269
|
|
|
@@ -275,13 +278,13 @@ class ChattyAssetContainerWithCollection(ChattyAssetBaseContainer[T, P], ABC):
|
|
|
275
278
|
self.set_preview_items(collection_items)
|
|
276
279
|
return collection_items
|
|
277
280
|
|
|
278
|
-
def get_by_query(self, query: dict, company_id: Optional[StrObjectId]) -> List[T]:
|
|
281
|
+
async def get_by_query(self, query: dict, company_id: Optional[StrObjectId]) -> List[T]:
|
|
279
282
|
logger.debug(f"{self.__class__.__name__} getting items by query {query} from collection")
|
|
280
|
-
return self.collection.get_docs(query=query, company_id=company_id)
|
|
283
|
+
return await self.collection.get_docs(query=query, company_id=company_id)
|
|
281
284
|
|
|
282
|
-
def get_deleted(self, company_id: Optional[StrObjectId]) -> List[T]:
|
|
285
|
+
async def get_deleted(self, company_id: Optional[StrObjectId]) -> List[T]:
|
|
283
286
|
logger.debug(f"{self.__class__.__name__} getting deleted items from collection")
|
|
284
|
-
return self.collection.get_docs(query={"deleted_at": {"$ne": None}}, company_id=company_id)
|
|
287
|
+
return await self.collection.get_docs(query={"deleted_at": {"$ne": None}}, company_id=company_id)
|
|
285
288
|
|
|
286
289
|
def load_from_db_thread(self, company_id: Optional[StrObjectId]):
|
|
287
290
|
"""We start a thread to load the items from the database so it doesn't block the main thread"""
|
|
@@ -292,21 +295,27 @@ class ChattyAssetContainerWithCollection(ChattyAssetBaseContainer[T, P], ABC):
|
|
|
292
295
|
thread.start()
|
|
293
296
|
|
|
294
297
|
def load_from_db(self, company_id: Optional[StrObjectId]):
|
|
295
|
-
"""Pass company_id=None to load all items from the database."""
|
|
298
|
+
"""Pass company_id=None to load all items from the database. Uses sync client for background loading."""
|
|
296
299
|
logger.debug(f"{self.__class__.__name__} loading items from collection")
|
|
297
|
-
#
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
300
|
+
# Background loading uses sync client (less critical, runs in thread)
|
|
301
|
+
query: Dict[str, Any] = {"deleted_at": None}
|
|
302
|
+
if company_id:
|
|
303
|
+
query["company_id"] = company_id
|
|
304
|
+
docs = list(self.collection.collection.find(filter=query))
|
|
305
|
+
# Create instances once and reuse
|
|
306
|
+
loaded_items = [self.collection.create_instance(doc) for doc in docs]
|
|
307
|
+
self.items = {item.id: item for item in loaded_items}
|
|
308
|
+
|
|
309
|
+
async def restore(self, id: str, execution_context: ExecutionContext) -> T:
|
|
301
310
|
logger.debug(f"{self.__class__.__name__} restoring item {id} with execution context {execution_context}")
|
|
302
311
|
if id in self.items:
|
|
303
312
|
raise ValueError(f"Item with id {id} already exists in {self.__class__.__name__}")
|
|
304
|
-
restored_item = self.collection.get_by_id(id)
|
|
313
|
+
restored_item = await self.collection.get_by_id(id)
|
|
305
314
|
if restored_item is None:
|
|
306
315
|
raise NotFoundError(f"Item with id {id} not found in collection DB")
|
|
307
316
|
restored_item.deleted_at = None
|
|
308
317
|
restored_item.update_now()
|
|
309
318
|
execution_context.set_event_time(restored_item.updated_at)
|
|
310
319
|
self.items[id] = restored_item
|
|
311
|
-
self.collection.update(restored_item)
|
|
312
|
-
return restored_item
|
|
320
|
+
await self.collection.update(restored_item)
|
|
321
|
+
return restored_item
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Asset Collection Subclasses
|
|
3
|
+
|
|
4
|
+
This module provides pre-configured AssetCollection subclasses for each asset type.
|
|
5
|
+
These ensure consistency between the API and microservices by defining the collection
|
|
6
|
+
configuration (collection name, asset type, preview type, create_instance_method) once.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .product_collection import ProductCollection
|
|
10
|
+
from .tag_collection import TagCollection
|
|
11
|
+
from .user_collection import UserCollection
|
|
12
|
+
from .chat_collection import ChatCollection
|
|
13
|
+
from .source_collection import SourceCollection
|
|
14
|
+
from .flow_collection import FlowCollection
|
|
15
|
+
from .sale_collection import SaleCollection
|
|
16
|
+
from .contact_point_collection import ContactPointCollection
|
|
17
|
+
from .ai_agent_collection import AiAgentCollection
|
|
18
|
+
from .fast_answer_collection import FastAnswerCollection
|
|
19
|
+
from .topic_collection import TopicCollection
|
|
20
|
+
from .filter_criteria_collection import FilterCriteriaCollection
|
|
21
|
+
from .ai_component_collection import AiComponentCollection
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
'ProductCollection',
|
|
25
|
+
'TagCollection',
|
|
26
|
+
'UserCollection',
|
|
27
|
+
'ChatCollection',
|
|
28
|
+
'SourceCollection',
|
|
29
|
+
'FlowCollection',
|
|
30
|
+
'SaleCollection',
|
|
31
|
+
'ContactPointCollection',
|
|
32
|
+
'AiAgentCollection',
|
|
33
|
+
'FastAnswerCollection',
|
|
34
|
+
'TopicCollection',
|
|
35
|
+
'FilterCriteriaCollection',
|
|
36
|
+
'AiComponentCollection',
|
|
37
|
+
]
|
|
38
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""AI Agent Collection - Pre-configured AssetCollection for AI Agents"""
|
|
2
|
+
|
|
3
|
+
from ..asset_service import AssetCollection
|
|
4
|
+
from ....models.company.assets.ai_agents_v2.chatty_ai_agent import ChattyAIAgent, ChattyAIAgentPreview
|
|
5
|
+
from ....models.data_base.mongo_connection import MongoConnection
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AiAgentCollection(AssetCollection[ChattyAIAgent, ChattyAIAgentPreview]):
|
|
9
|
+
"""Pre-configured collection for AI Agent assets"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, connection: MongoConnection):
|
|
12
|
+
super().__init__(
|
|
13
|
+
collection="ai_agents",
|
|
14
|
+
asset_type=ChattyAIAgent,
|
|
15
|
+
connection=connection,
|
|
16
|
+
create_instance_method=ChattyAIAgent.default_create_instance_method,
|
|
17
|
+
preview_type=ChattyAIAgentPreview
|
|
18
|
+
)
|
|
19
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""AI Agent In Chat Collection - Pre-configured AssetCollection for AI Agent state in chats"""
|
|
2
|
+
|
|
3
|
+
from ..asset_service import AssetCollection
|
|
4
|
+
from ....models.company.assets.ai_agents_v2.chatty_ai_agent_in_chat import ChattyAIAgentInChat
|
|
5
|
+
from ....models.data_base.mongo_connection import MongoConnection
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AIAgentInChatCollection(AssetCollection[ChattyAIAgentInChat, ChattyAIAgentInChat]):
|
|
10
|
+
"""
|
|
11
|
+
Pre-configured collection for AI Agent In Chat state.
|
|
12
|
+
|
|
13
|
+
This is a standalone collection (not embedded in Chat) that allows Lambda to
|
|
14
|
+
manage AI agent state independently without loading entire chat documents.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, connection: MongoConnection):
|
|
18
|
+
super().__init__(
|
|
19
|
+
collection="chatty_ai_agents_in_chat",
|
|
20
|
+
asset_type=ChattyAIAgentInChat,
|
|
21
|
+
connection=connection,
|
|
22
|
+
create_instance_method=lambda doc: ChattyAIAgentInChat(**doc),
|
|
23
|
+
preview_type=ChattyAIAgentInChat # No separate preview type needed
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
async def get_by_chat_id(self, chat_id: str) -> Optional[ChattyAIAgentInChat]:
|
|
27
|
+
"""Get AI agent state by chat ID, or None if not found"""
|
|
28
|
+
doc = await self.async_collection.find_one({"chat_id": chat_id, "deleted_at": None})
|
|
29
|
+
if not doc:
|
|
30
|
+
return None
|
|
31
|
+
return self.create_instance(doc)
|
|
32
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""AI Component Collection - Pre-configured AssetCollection for AI Components"""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from ..asset_service import AssetCollection
|
|
5
|
+
from ....models.base_models.ai_agent_component import AiAgentComponent, AiAgentComponentPreview
|
|
6
|
+
from ....models.data_base.mongo_connection import MongoConnection
|
|
7
|
+
from ....services.ai_components_service import AiComponentsService
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AiComponentCollection(AssetCollection[AiAgentComponent, AiAgentComponentPreview]):
|
|
11
|
+
"""Pre-configured collection for AI Component assets"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, connection: MongoConnection):
|
|
14
|
+
super().__init__(
|
|
15
|
+
collection="ai_components",
|
|
16
|
+
asset_type=AiAgentComponent,
|
|
17
|
+
connection=connection,
|
|
18
|
+
create_instance_method=AiComponentsService.instantiate_component,
|
|
19
|
+
preview_type=AiAgentComponentPreview
|
|
20
|
+
)
|
|
21
|
+
|