diff --git a/backend/database/apps.py b/backend/database/apps.py index 5c2ce6284d..2079981242 100644 --- a/backend/database/apps.py +++ b/backend/database/apps.py @@ -9,29 +9,31 @@ from models.app import UsageHistoryType from ._client import db -from .redis_db import get_plugin_reviews +from .redis_db import get_app_reviews # ***************************** # ********** CRUD ************* # ***************************** -omi_plugins_bucket = os.getenv('BUCKET_PLUGINS_LOGOS') +apps_collection = 'plugins_data' +app_analytics_collection = 'plugins' +testers_collection = 'testers' def migrate_reviews_from_redis_to_firestore(): - apps_ref = db.collection('plugins_data').stream() + apps_ref = db.collection(apps_collection).stream() for app in apps_ref: print('migrating reviews for app:', app.id) app_id = app.id - reviews = get_plugin_reviews(app_id) + reviews = get_app_reviews(app_id) for uid, review in reviews.items(): review['app_id'] = app_id - new_app_ref = db.collection('plugins_data').document(app_id).collection('reviews').document(uid) + new_app_ref = db.collection(apps_collection).document(app_id).collection('reviews').document(uid) new_app_ref.set(review) def get_app_by_id_db(app_id: str): - app_ref = db.collection('plugins_data').document(app_id) + app_ref = db.collection(apps_collection).document(app_id) doc = app_ref.get() if doc.exists: if doc.to_dict().get('deleted', True): @@ -46,13 +48,13 @@ def get_audio_apps_count(app_ids: List[str]): return 0 filters = [FieldFilter('id', 'in', app_ids), FieldFilter('deleted', '==', False), FieldFilter('external_integration.triggers_on', '==', 'audio_bytes')] - apps_ref = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).count().get() + apps_ref = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).count().get() return apps_ref[0][0].value def get_private_apps_db(uid: str) -> List: filters = [FieldFilter('uid', '==', uid), FieldFilter('private', '==', True), FieldFilter('deleted', '==', False)] - private_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream() + private_apps = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).stream() data = [doc.to_dict() for doc in private_apps] return data @@ -61,40 +63,40 @@ def get_private_apps_db(uid: str) -> List: def get_unapproved_public_apps_db() -> List: filters = [FieldFilter('approved', '==', False), FieldFilter('private', '==', False), FieldFilter('deleted', '==', False)] - public_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream() + public_apps = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).stream() return [doc.to_dict() for doc in public_apps] # This returns all unapproved apps of all users including private apps def get_all_unapproved_apps_db() -> List: filters = [FieldFilter('approved', '==', False), FieldFilter('deleted', '==', False)] - all_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream() + all_apps = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).stream() return [doc.to_dict() for doc in all_apps] def get_public_apps_db(uid: str) -> List: - public_plugins = db.collection('plugins_data').stream() - data = [doc.to_dict() for doc in public_plugins] + public_apps = db.collection(apps_collection).stream() + data = [doc.to_dict() for doc in public_apps] - return [plugin for plugin in data if plugin.get('approved') == True or plugin.get('uid') == uid] + return [app for app in data if app.get('approved') == True or app.get('uid') == uid] def get_public_approved_apps_db() -> List: filters = [FieldFilter('approved', '==', True), FieldFilter('private', '==', False), FieldFilter('deleted', '==', False)] - public_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream() + public_apps = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).stream() return [doc.to_dict() for doc in public_apps] def get_popular_apps_db() -> List: filters = [FieldFilter('approved', '==', True), FieldFilter('deleted', '==', False), FieldFilter('is_popular', '==', True)] - popular_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream() + popular_apps = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).stream() return [doc.to_dict() for doc in popular_apps] def set_app_popular_db(app_id: str, popular: bool): - app_ref = db.collection('plugins_data').document(app_id) + app_ref = db.collection(apps_collection).document(app_id) app_ref.update({'is_popular': popular}) @@ -102,12 +104,12 @@ def set_app_popular_db(app_id: str, popular: bool): def get_public_unapproved_apps_db(uid: str) -> List: filters = [FieldFilter('approved', '==', False), FieldFilter('uid', '==', uid), FieldFilter('deleted', '==', False), FieldFilter('private', '==', False)] - public_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream() + public_apps = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).stream() return [doc.to_dict() for doc in public_apps] def get_apps_for_tester_db(uid: str) -> List: - tester_ref = db.collection('testers').document(uid) + tester_ref = db.collection(testers_collection).document(uid) doc = tester_ref.get() if doc.exists: apps = doc.to_dict().get('apps', []) @@ -115,75 +117,75 @@ def get_apps_for_tester_db(uid: str) -> List: return [] filters = [FieldFilter('approved', '==', False), FieldFilter('id', 'in', apps), FieldFilter('deleted', '==', False)] - public_apps = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream() + public_apps = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).stream() return [doc.to_dict() for doc in public_apps] return [] def add_app_to_db(app_data: dict): - app_ref = db.collection('plugins_data') + app_ref = db.collection(apps_collection) app_ref.add(app_data, app_data['id']) def upsert_app_to_db(app_data: dict): - app_ref = db.collection('plugins_data').document(app_data['id']) + app_ref = db.collection(apps_collection).document(app_data['id']) app_ref.set(app_data) def update_app_in_db(app_data: dict): - app_ref = db.collection('plugins_data').document(app_data['id']) + app_ref = db.collection(apps_collection).document(app_data['id']) app_ref.update(app_data) def delete_app_from_db(app_id: str): - app_ref = db.collection('plugins_data').document(app_id) + app_ref = db.collection(apps_collection).document(app_id) app_ref.update({'deleted': True}) def update_app_visibility_in_db(app_id: str, private: bool): - app_ref = db.collection('plugins_data').document(app_id) + app_ref = db.collection(apps_collection).document(app_id) if 'private' in app_id and not private: app = app_ref.get().to_dict() app_ref.delete() new_app_id = app_id.split('-private')[0] + '-' + str(ULID()) app['id'] = new_app_id app['private'] = private - app_ref = db.collection('plugins_data').document(new_app_id) + app_ref = db.collection(apps_collection).document(new_app_id) app_ref.set(app) else: app_ref.update({'private': private}) -def change_app_approval_status(plugin_id: str, approved: bool): - plugin_ref = db.collection('plugins_data').document(plugin_id) - plugin_ref.update({'approved': approved, 'status': 'approved' if approved else 'rejected'}) +def change_app_approval_status(app_id: str, approved: bool): + app_ref = db.collection(apps_collection).document(app_id) + app_ref.update({'approved': approved, 'status': 'approved' if approved else 'rejected'}) def get_app_usage_history_db(app_id: str): - usage = db.collection('plugins').document(app_id).collection('usage_history').stream() + usage = db.collection(app_analytics_collection).document(app_id).collection('usage_history').stream() return [doc.to_dict() for doc in usage] def get_app_memory_created_integration_usage_count_db(app_id: str): - usage = db.collection('plugins').document(app_id).collection('usage_history').where( + usage = db.collection(app_analytics_collection).document(app_id).collection('usage_history').where( filter=FieldFilter('type', '==', UsageHistoryType.memory_created_external_integration)).count().get() return usage[0][0].value def get_app_memory_prompt_usage_count_db(app_id: str): - usage = db.collection('plugins').document(app_id).collection('usage_history').where( + usage = db.collection(app_analytics_collection).document(app_id).collection('usage_history').where( filter=FieldFilter('type', '==', UsageHistoryType.memory_created_prompt)).count().get() return usage[0][0].value def get_app_chat_message_sent_usage_count_db(app_id: str): - usage = db.collection('plugins').document(app_id).collection('usage_history').where( + usage = db.collection(app_analytics_collection).document(app_id).collection('usage_history').where( filter=FieldFilter('type', '==', UsageHistoryType.chat_message_sent)).count().get() return usage[0][0].value def get_app_usage_count_db(app_id: str): - usage = db.collection('plugins').document(app_id).collection('usage_history').count().get() + usage = db.collection(app_analytics_collection).document(app_id).collection('usage_history').count().get() return usage[0][0].value @@ -192,7 +194,7 @@ def get_app_usage_count_db(app_id: str): # ******************************** def set_app_review_in_db(app_id: str, uid: str, review: dict): - app_ref = db.collection('plugins_data').document(app_id).collection('reviews').document(uid) + app_ref = db.collection(apps_collection).document(app_id).collection('reviews').document(uid) app_ref.set(review) @@ -201,27 +203,27 @@ def set_app_review_in_db(app_id: str, uid: str, review: dict): # ******************************** def add_tester_db(data: dict): - app_ref = db.collection('testers').document(data['uid']) + app_ref = db.collection(testers_collection).document(data['uid']) app_ref.set(data) def add_app_access_for_tester_db(app_id: str, uid: str): - app_ref = db.collection('testers').document(uid) + app_ref = db.collection(testers_collection).document(uid) app_ref.update({'apps': ArrayUnion([app_id])}) def remove_app_access_for_tester_db(app_id: str, uid: str): - app_ref = db.collection('testers').document(uid) + app_ref = db.collection(testers_collection).document(uid) app_ref.update({'apps': ArrayRemove([app_id])}) def remove_tester_db(uid: str): - app_ref = db.collection('testers').document(uid) + app_ref = db.collection(testers_collection).document(uid) app_ref.delete() def can_tester_access_app_db(app_id: str, uid: str) -> bool: - app_ref = db.collection('testers').document(uid) + app_ref = db.collection(testers_collection).document(uid) doc = app_ref.get() if doc.exists: return app_id in doc.to_dict().get('apps', []) @@ -229,7 +231,7 @@ def can_tester_access_app_db(app_id: str, uid: str) -> bool: def is_tester_db(uid: str) -> bool: - app_ref = db.collection('testers').document(uid) + app_ref = db.collection(testers_collection).document(uid) return app_ref.get().exists @@ -252,7 +254,7 @@ def record_app_usage( 'type': usage_type, } - db.collection('plugins').document(app_id).collection('usage_history').document(conversation_id or message_id).set( + db.collection(app_analytics_collection).document(app_id).collection('usage_history').document(conversation_id or message_id).set( data) return data @@ -262,12 +264,12 @@ def record_app_usage( # ******************************** def delete_persona_db(persona_id: str): - persona_ref = db.collection('plugins_data').document(persona_id) + persona_ref = db.collection(apps_collection).document(persona_id) persona_ref.update({'deleted': True}) def get_personas_by_username_db(persona_id: str): - persona_ref = db.collection('plugins_data').where('username', '==', persona_id) + persona_ref = db.collection(apps_collection).where('username', '==', persona_id) docs = persona_ref.get() if not docs: return None @@ -277,7 +279,7 @@ def get_personas_by_username_db(persona_id: str): def get_persona_by_username_db(username: str): filters = [FieldFilter('username', '==', username), FieldFilter('capabilities', 'array_contains', 'persona'), FieldFilter('deleted', '==', False)] - persona_ref = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).limit(1) + persona_ref = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).limit(1) docs = persona_ref.get() if not docs: return None @@ -288,7 +290,7 @@ def get_persona_by_username_db(username: str): def get_persona_by_id_db(persona_id: str): - persona_ref = db.collection('plugins_data').document(persona_id) + persona_ref = db.collection(apps_collection).document(persona_id) doc = persona_ref.get() if doc.exists: return doc.to_dict() @@ -298,7 +300,7 @@ def get_persona_by_id_db(persona_id: str): def get_persona_by_uid_db(uid: str): filters = [FieldFilter('uid', '==', uid), FieldFilter('capabilities', 'array_contains', 'persona'), FieldFilter('deleted', '==', False)] - persona_ref = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).limit(1) + persona_ref = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).limit(1) docs = persona_ref.get() if not docs: return None @@ -315,7 +317,7 @@ def get_user_persona_by_uid(uid: str): FieldFilter('deleted', '==', False), FieldFilter('uid', '==', uid), ] - persona_ref = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).limit(1) + persona_ref = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).limit(1) docs = persona_ref.get() if not docs: return None @@ -327,7 +329,7 @@ def get_user_persona_by_uid(uid: str): def create_user_persona_db(persona_data: dict): """Create a new user persona in the database""" - persona_ref = db.collection('plugins_data') + persona_ref = db.collection(apps_collection) persona_ref.add(persona_data, persona_data['id']) return persona_data @@ -338,7 +340,7 @@ def get_persona_by_twitter_handle_db(handle: str): FieldFilter('deleted', '==', False), FieldFilter('twitter.username', '==', handle) ] - persona_ref = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).limit(1) + persona_ref = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).limit(1) docs = persona_ref.get() if not docs: return None @@ -355,7 +357,7 @@ def get_persona_by_username_twitter_handle_db(username: str, handle: str): FieldFilter('deleted', '==', False), FieldFilter('twitter.username', '==', handle) ] - persona_ref = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).limit(1) + persona_ref = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).limit(1) docs = persona_ref.get() if not docs: return None @@ -368,7 +370,7 @@ def get_persona_by_username_twitter_handle_db(username: str, handle: str): def get_omi_personas_by_uid_db(uid: str): filters = [FieldFilter('uid', '==', uid), FieldFilter('capabilities', 'array_contains', 'persona'), FieldFilter('deleted', '==', False)] - persona_ref = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)) + persona_ref = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)) docs = persona_ref.get() if not docs: return [] @@ -380,7 +382,7 @@ def get_omi_persona_apps_by_uid_db(uid: str): filters = [FieldFilter('uid', '==', uid), FieldFilter('category', '==', 'personality-emulation'), FieldFilter('deleted', '==', False)] - persona_ref = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)) + persona_ref = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)) docs = persona_ref.get() if not docs: return [] @@ -389,33 +391,33 @@ def get_omi_persona_apps_by_uid_db(uid: str): def add_persona_to_db(persona_data: dict): - persona_ref = db.collection('plugins_data') + persona_ref = db.collection(apps_collection) persona_ref.add(persona_data, persona_data['id']) def update_persona_in_db(persona_data: dict): - persona_ref = db.collection('plugins_data').document(persona_data['id']) + persona_ref = db.collection(apps_collection).document(persona_data['id']) persona_ref.update(persona_data) def migrate_app_owner_id_db(new_id: str, old_id: str): filters = [FieldFilter('uid', '==', old_id), FieldFilter('deleted', '==', False)] - apps_ref = db.collection('plugins_data').where(filter=BaseCompositeFilter('AND', filters)).stream() + apps_ref = db.collection(apps_collection).where(filter=BaseCompositeFilter('AND', filters)).stream() for app in apps_ref: - app_ref = db.collection('plugins_data').document(app.id) + app_ref = db.collection(apps_collection).document(app.id) app_ref.update({'uid': new_id}) def create_api_key_db(app_id: str, api_key_data: dict): """Create a new API key for an app in the database""" - api_key_ref = db.collection('plugins_data').document(app_id).collection('api_keys').document(api_key_data['id']) + api_key_ref = db.collection(apps_collection).document(app_id).collection('api_keys').document(api_key_data['id']) api_key_ref.set(api_key_data) return api_key_data def get_api_key_by_id_db(app_id: str, key_id: str): """Get an API key by its ID""" - api_key_ref = db.collection('plugins_data').document(app_id).collection('api_keys').document(key_id) + api_key_ref = db.collection(apps_collection).document(app_id).collection('api_keys').document(key_id) doc = api_key_ref.get() if doc.exists: return doc.to_dict() @@ -425,7 +427,7 @@ def get_api_key_by_id_db(app_id: str, key_id: str): def get_api_key_by_hash_db(app_id: str, hashed_key: str): """Get an API key by its hash value""" filters = [FieldFilter('hashed', '==', hashed_key)] - api_keys_ref = db.collection('plugins_data').document(app_id).collection('api_keys').where( + api_keys_ref = db.collection(apps_collection).document(app_id).collection('api_keys').where( filter=BaseCompositeFilter('AND', filters)).limit(1) docs = api_keys_ref.get() if not docs: @@ -438,13 +440,13 @@ def get_api_key_by_hash_db(app_id: str, hashed_key: str): def list_api_keys_db(app_id: str): """List all API keys for an app (excluding the hashed values)""" - api_keys_ref = db.collection('plugins_data').document(app_id).collection('api_keys').order_by('created_at', - direction='DESCENDING').stream() + api_keys_ref = db.collection(apps_collection).document(app_id).collection('api_keys').order_by('created_at', + direction='DESCENDING').stream() return [{k: v for k, v in doc.to_dict().items() if k != 'hashed'} for doc in api_keys_ref] def delete_api_key_db(app_id: str, key_id: str): """Delete an API key""" - api_key_ref = db.collection('plugins_data').document(app_id).collection('api_keys').document(key_id) + api_key_ref = db.collection(apps_collection).document(app_id).collection('api_keys').document(key_id) api_key_ref.delete() return True diff --git a/backend/database/chat.py b/backend/database/chat.py index 238abc3ee5..2905abe44c 100644 --- a/backend/database/chat.py +++ b/backend/database/chat.py @@ -19,13 +19,13 @@ def add_message(uid: str, message_data: dict): return message_data -def add_plugin_message(text: str, plugin_id: str, uid: str, conversation_id: Optional[str] = None) -> Message: +def add_app_message(text: str, app_id: str, uid: str, conversation_id: Optional[str] = None) -> Message: ai_message = Message( id=str(uuid.uuid4()), text=text, created_at=datetime.now(timezone.utc), sender='ai', - plugin_id=plugin_id, + app_id=app_id, from_external_integration=False, type='text', memories_id=[conversation_id] if conversation_id else [], @@ -40,7 +40,7 @@ def add_summary_message(text: str, uid: str) -> Message: text=text, created_at=datetime.now(timezone.utc), sender='ai', - plugin_id=None, + app_id=None, from_external_integration=False, type='day_summary', memories_id=[], @@ -49,11 +49,11 @@ def add_summary_message(text: str, uid: str) -> Message: return ai_message -def get_plugin_messages(uid: str, plugin_id: str, limit: int = 20, offset: int = 0, include_conversations: bool = False): +def get_app_messages(uid: str, app_id: str, limit: int = 20, offset: int = 0, include_conversations: bool = False): user_ref = db.collection('users').document(uid) messages_ref = ( user_ref.collection('messages') - .where(filter=FieldFilter('plugin_id', '==', plugin_id)) + .where(filter=FieldFilter('plugin_id', '==', app_id)) .order_by('created_at', direction=firestore.Query.DESCENDING) .limit(limit) .offset(offset) @@ -87,7 +87,8 @@ def get_plugin_messages(uid: str, plugin_id: str, limit: int = 20, offset: int = # Attach conversations to messages for message in messages: message['memories'] = [ - conversations[conversation_id] for conversation_id in message.get('memories_id', []) if conversation_id in conversations + conversations[conversation_id] for conversation_id in message.get('memories_id', []) if + conversation_id in conversations ] return messages @@ -95,17 +96,18 @@ def get_plugin_messages(uid: str, plugin_id: str, limit: int = 20, offset: int = @timeit def get_messages( - uid: str, limit: int = 20, offset: int = 0, include_conversations: bool = False, plugin_id: Optional[str] = None, chat_session_id: Optional[str] = None + uid: str, limit: int = 20, offset: int = 0, include_conversations: bool = False, app_id: Optional[str] = None, + chat_session_id: Optional[str] = None # include_plugin_id_filter: bool = True, ): - print('get_messages', uid, limit, offset, plugin_id, include_conversations) + print('get_messages', uid, limit, offset, app_id, include_conversations) user_ref = db.collection('users').document(uid) messages_ref = ( user_ref.collection('messages') .where(filter=FieldFilter('deleted', '==', False)) ) # if include_plugin_id_filter: - messages_ref = messages_ref.where(filter=FieldFilter('plugin_id', '==', plugin_id)) + messages_ref = messages_ref.where(filter=FieldFilter('plugin_id', '==', app_id)) if chat_session_id: messages_ref = messages_ref.where(filter=FieldFilter('chat_session_id', '==', chat_session_id)) @@ -140,7 +142,8 @@ def get_messages( # Attach conversations to messages for message in messages: message['memories'] = [ - conversations[conversation_id] for conversation_id in message.get('memories_id', []) if conversation_id in conversations + conversations[conversation_id] for conversation_id in message.get('memories_id', []) if + conversation_id in conversations ] # Fetch file chat @@ -190,15 +193,16 @@ def report_message(uid: str, msg_doc_id: str): return {"message": f"Update failed: {e}"} -def batch_delete_messages(parent_doc_ref, batch_size=450, plugin_id: Optional[str] = None, chat_session_id: Optional[str] = None): +def batch_delete_messages(parent_doc_ref, batch_size=450, app_id: Optional[str] = None, + chat_session_id: Optional[str] = None): messages_ref = ( parent_doc_ref.collection('messages') .where(filter=FieldFilter('deleted', '==', False)) ) - messages_ref = messages_ref.where(filter=FieldFilter('plugin_id', '==', plugin_id)) + messages_ref = messages_ref.where(filter=FieldFilter('plugin_id', '==', app_id)) if chat_session_id: messages_ref = messages_ref.where(filter=FieldFilter('chat_session_id', '==', chat_session_id)) - print('batch_delete_messages', plugin_id) + print('batch_delete_messages', app_id) last_doc = None # For pagination while True: @@ -234,11 +238,12 @@ def clear_chat(uid: str, app_id: Optional[str] = None, chat_session_id: Optional print(f"Deleting messages for user: {uid}") if not user_ref.get().exists: return {"message": "User not found"} - batch_delete_messages(user_ref, plugin_id=app_id, chat_session_id=chat_session_id) + batch_delete_messages(user_ref, app_id=app_id, chat_session_id=chat_session_id) return None except Exception as e: return {"message": str(e)} + def add_multi_files(uid: str, files_data: list): batch = db.batch() user_ref = db.collection('users').document(uid) @@ -250,6 +255,7 @@ def add_multi_files(uid: str, files_data: list): batch.commit() + def get_chat_files(uid: str, files_id: List[str] = []): files_ref = ( db.collection('users').document(uid).collection('files') @@ -272,17 +278,19 @@ def delete_multi_files(uid: str, files_data: list): batch.commit() + def add_chat_session(uid: str, chat_session_data: dict): chat_session_data['deleted'] = False user_ref = db.collection('users').document(uid) user_ref.collection('chat_sessions').document(chat_session_data['id']).set(chat_session_data) return chat_session_data -def get_chat_session(uid: str, plugin_id: Optional[str] = None): + +def get_chat_session(uid: str, app_id: Optional[str] = None): session_ref = ( db.collection('users').document(uid).collection('chat_sessions') .where(filter=FieldFilter('deleted', '==', False)) - .where(filter=FieldFilter('plugin_id', '==', plugin_id)) + .where(filter=FieldFilter('plugin_id', '==', app_id)) .limit(1) ) @@ -292,16 +300,19 @@ def get_chat_session(uid: str, plugin_id: Optional[str] = None): return None + def delete_chat_session(uid, chat_session_id): user_ref = db.collection('users').document(uid) session_ref = user_ref.collection('chat_sessions').document(chat_session_id) session_ref.update({'deleted': True}) + def add_message_to_chat_session(uid: str, chat_session_id: str, message_id: str): user_ref = db.collection('users').document(uid) session_ref = user_ref.collection('chat_sessions').document(chat_session_id) session_ref.update({"message_ids": firestore.ArrayUnion([message_id])}) + def add_files_to_chat_session(uid: str, chat_session_id: str, file_ids: List[str]): if not file_ids: return diff --git a/backend/database/mem_db.py b/backend/database/mem_db.py index 042ae1cebf..afd132fd31 100644 --- a/backend/database/mem_db.py +++ b/backend/database/mem_db.py @@ -1,13 +1,13 @@ import time -proactive_noti_sent_at = {} # {: (ts, ex)} +proactive_noti_sent_at = {} # {: (ts, ex)} -def set_proactive_noti_sent_at(uid: str, plugin_id: str, ts: int, ttl: int = 30): - k = f'{uid}:{plugin_id}' +def set_proactive_noti_sent_at(uid: str, app_id: str, ts: int, ttl: int = 30): + k = f'{uid}:{app_id}' proactive_noti_sent_at[k] = (ts, ttl + time.time()) -def get_proactive_noti_sent_at(uid: str, plugin_id: str): - k = f'{uid}:{plugin_id}' +def get_proactive_noti_sent_at(uid: str, app_id: str): + k = f'{uid}:{app_id}' if k not in proactive_noti_sent_at: return None diff --git a/backend/database/redis_db.py b/backend/database/redis_db.py index e580f1e21f..e18915e665 100644 --- a/backend/database/redis_db.py +++ b/backend/database/redis_db.py @@ -185,9 +185,9 @@ def get_specific_user_review(app_id: str, uid: str) -> dict: return reviews.get(uid, {}) -def migrate_user_plugins_reviews(prev_uid: str, new_uid: str): +def migrate_user_apps_reviews(prev_uid: str, new_uid: str): for key in r.scan_iter(f'plugins:*:reviews'): - plugin_id = key.decode().split(':')[1] + app_id = key.decode().split(':')[1] reviews = r.get(key) if not reviews: continue @@ -195,7 +195,7 @@ def migrate_user_plugins_reviews(prev_uid: str, new_uid: str): if prev_uid in reviews: reviews[new_uid] = reviews.pop(prev_uid) reviews[new_uid]['uid'] = new_uid - r.set(f'plugins:{plugin_id}:reviews', str(reviews)) + r.set(f'plugins:{app_id}:reviews', str(reviews)) def set_user_paid_app(app_id: str, uid: str, ttl: int): @@ -217,36 +217,36 @@ def disable_app(uid: str, app_id: str): r.srem(f'users:{uid}:enabled_plugins', app_id) -def get_enabled_plugins(uid: str): +def get_enabled_apps(uid: str): val = r.smembers(f'users:{uid}:enabled_plugins') if not val: return [] return [x.decode() for x in val] -def get_plugin_reviews(plugin_id: str) -> dict: - reviews = r.get(f'plugins:{plugin_id}:reviews') +def get_app_reviews(app_id: str) -> dict: + reviews = r.get(f'plugins:{app_id}:reviews') if not reviews: return {} return eval(reviews) -def get_plugins_reviews(plugin_ids: list) -> dict: - if not plugin_ids: +def get_apps_reviews(app_ids: list) -> dict: + if not app_ids: return {} - keys = [f'plugins:{plugin_id}:reviews' for plugin_id in plugin_ids] + keys = [f'plugins:{app_id}:reviews' for app_id in app_ids] reviews = r.mget(keys) if reviews is None: return {} return { - plugin_id: eval(review) if review else {} - for plugin_id, review in zip(plugin_ids, reviews) + app_id: eval(review) if review else {} + for app_id, review in zip(app_ids, reviews) } -def set_plugin_installs_count(plugin_id: str, count: int): - r.set(f'plugins:{plugin_id}:installs', count) +def set_app_installs_count(app_id: str, count: int): + r.set(f'plugins:{app_id}:installs', count) def increase_app_installs_count(app_id: str): @@ -257,24 +257,24 @@ def decrease_app_installs_count(app_id: str): r.decr(f'plugins:{app_id}:installs') -def get_plugin_installs_count(plugin_id: str) -> int: - count = r.get(f'plugins:{plugin_id}:installs') +def get_app_installs_count(app_id: str) -> int: + count = r.get(f'plugins:{app_id}:installs') if not count: return 0 return int(count) -def get_plugins_installs_count(plugin_ids: list) -> dict: - if not plugin_ids: +def get_apps_installs_count(app_ids: list) -> dict: + if not app_ids: return {} - keys = [f'plugins:{plugin_id}:installs' for plugin_id in plugin_ids] + keys = [f'plugins:{app_id}:installs' for app_id in app_ids] counts = r.mget(keys) if counts is None: return {} return { - plugin_id: int(count) if count else 0 - for plugin_id, count in zip(plugin_ids, counts) + app_id: int(count) if count else 0 + for app_id, count in zip(app_ids, counts) } @@ -460,19 +460,19 @@ def has_migrated_retrieval_conversation_id(conversation_id: str) -> bool: return r.sismember('migrated_retrieval_memory_ids', conversation_id) -def set_proactive_noti_sent_at(uid: str, plugin_id: str, ts: int, ttl: int = 30): - r.set(f'{uid}:{plugin_id}:proactive_noti_sent_at', ts, ex=ttl) +def set_proactive_noti_sent_at(uid: str, app_id: str, ts: int, ttl: int = 30): + r.set(f'{uid}:{app_id}:proactive_noti_sent_at', ts, ex=ttl) -def get_proactive_noti_sent_at(uid: str, plugin_id: str): - val = r.get(f'{uid}:{plugin_id}:proactive_noti_sent_at') +def get_proactive_noti_sent_at(uid: str, app_id: str): + val = r.get(f'{uid}:{app_id}:proactive_noti_sent_at') if not val: return None return int(val) -def get_proactive_noti_sent_at_ttl(uid: str, plugin_id: str): - return r.ttl(f'{uid}:{plugin_id}:proactive_noti_sent_at') +def get_proactive_noti_sent_at_ttl(uid: str, app_id: str): + return r.ttl(f'{uid}:{app_id}:proactive_noti_sent_at') def set_user_preferred_app(uid: str, app_id: str): diff --git a/backend/models/chat.py b/backend/models/chat.py index 998e497062..9cbecb34eb 100644 --- a/backend/models/chat.py +++ b/backend/models/chat.py @@ -1,8 +1,8 @@ from datetime import datetime from enum import Enum -from typing import List, Optional +from typing import List, Optional, Any -from pydantic import BaseModel +from pydantic import BaseModel, model_validator class MessageSender(str, Enum): @@ -48,6 +48,8 @@ class Message(BaseModel): text: str created_at: datetime sender: MessageSender + app_id: Optional[str] = None + # TODO: remove plugin_id after migration plugin_id: Optional[str] = None from_external_integration: bool = False type: MessageType @@ -60,6 +62,19 @@ class Message(BaseModel): files: List[FileChat] = [] chat_session_id: Optional[str] = None + @model_validator(mode='before') + @classmethod + def _sync_app_and_plugin_ids(cls, data: Any) -> Any: + if isinstance(data, dict): + app_id_val = data.get('app_id') + plugin_id_val = data.get('plugin_id') + + if app_id_val is not None: + data['plugin_id'] = app_id_val + elif plugin_id_val is not None: + data['app_id'] = plugin_id_val + return data + @staticmethod def get_messages_as_string( messages: List['Message'], @@ -71,11 +86,11 @@ def get_messages_as_string( def get_sender_name(message: Message) -> str: if message.sender == 'human': return 'User' - # elif use_plugin_name_if_available and message.plugin_id is not None: - # plugin = next((p for p in plugins if p.id == message.plugin_id), None) + # elif use_plugin_name_if_available and message.app_id is not None: + # plugin = next((p for p in plugins if p.id == message.app_id), None) # if plugin: # return plugin.name RESTORE ME - return message.sender.upper() # TODO: use plugin id + return message.sender.upper() # TODO: use app id formatted_messages = [ f"({message.created_at.strftime('%d %b %Y at %H:%M UTC')}) {get_sender_name(message)}: {message.text}" @@ -95,11 +110,11 @@ def get_messages_as_xml( def get_sender_name(message: Message) -> str: if message.sender == 'human': return 'User' - # elif use_plugin_name_if_available and message.plugin_id is not None: - # plugin = next((p for p in plugins if p.id == message.plugin_id), None) + # elif use_plugin_name_if_available and message.app_id is not None: + # plugin = next((p for p in plugins if p.id == message.app_id), None) # if plugin: # return plugin.name RESTORE ME - return message.sender.upper() # TODO: use plugin id + return message.sender.upper() # TODO: use app id formatted_messages = [ f""" @@ -135,10 +150,24 @@ class ChatSession(BaseModel): id: str message_ids: Optional[List[str]] = [] file_ids: Optional[List[str]] = [] + app_id: Optional[str] = None plugin_id: Optional[str] = None created_at: datetime deleted: bool = False + @model_validator(mode='before') + @classmethod + def _sync_chat_session_app_and_plugin_ids(cls, data: Any) -> Any: + if isinstance(data, dict): + app_id_val = data.get('app_id') + plugin_id_val = data.get('plugin_id') + + if app_id_val is not None: + data['plugin_id'] = app_id_val + elif plugin_id_val is not None: + data['app_id'] = plugin_id_val + return data + def add_file_ids(self, new_file_ids: List[str]): if self.file_ids is None: self.file_ids = [] diff --git a/backend/models/memory.py b/backend/models/memory.py deleted file mode 100644 index 7d588da9f5..0000000000 --- a/backend/models/memory.py +++ /dev/null @@ -1,301 +0,0 @@ -# Memories are now conversations, and the models are now in the models/conversation.py file - -# MIGRATE: This file is deprecated and will be removed in the future. -# Please refer to the models/conversation.py file for the latest models. -# This file is only used by the current /v1/memories and /v2/memories endpoints, which will be deprecated soon. - -from datetime import datetime, timezone -from enum import Enum -from typing import List, Optional, Dict - -from pydantic import BaseModel, Field - -from models.chat import Message -from models.transcript_segment import TranscriptSegment - - -class CategoryEnum(str, Enum): - personal = 'personal' - education = 'education' - health = 'health' - finance = 'finance' - legal = 'legal' - philosophy = 'philosophy' - spiritual = 'spiritual' - science = 'science' - entrepreneurship = 'entrepreneurship' - parenting = 'parenting' - romance = 'romantic' - travel = 'travel' - inspiration = 'inspiration' - technology = 'technology' - business = 'business' - social = 'social' - work = 'work' - sports = 'sports' - politics = 'politics' - literature = 'literature' - history = 'history' - architecture = 'architecture' - # Added at 2024-01-23 - music = 'music' - weather = 'weather' - news = 'news' - entertainment = 'entertainment' - psychology = 'psychology' - real = 'real' - design = 'design' - family = 'family' - economics = 'economics' - environment = 'environment' - other = 'other' - - -class UpdateMemory(BaseModel): - title: Optional[str] = None - overview: Optional[str] = None - - -class MemoryPhoto(BaseModel): - base64: str - description: str - - -class PluginResult(BaseModel): - plugin_id: Optional[str] - content: str - - -class ActionItem(BaseModel): - description: str = Field(description="The action item to be completed") - completed: bool = False # IGNORE ME from the model parser - deleted: bool = False - - -class Event(BaseModel): - title: str = Field(description="The title of the event") - description: str = Field(description="A brief description of the event", default='') - start: datetime = Field(description="The start date and time of the event") - duration: int = Field(description="The duration of the event in minutes", default=30) - created: bool = False - - def as_dict_cleaned_dates(self): - event_dict = self.dict() - event_dict['start'] = event_dict['start'].isoformat() - return event_dict - - -class Structured(BaseModel): - title: str = Field(description="A title/name for this conversation", default='') - overview: str = Field( - description="A brief overview of the conversation, highlighting the key details from it", - default='', - ) - emoji: str = Field(description="An emoji to represent the memory", default='🧠') - category: CategoryEnum = Field(description="A category for this memory", default=CategoryEnum.other) - action_items: List[ActionItem] = Field(description="A list of action items from the conversation", default=[]) - events: List[Event] = Field( - description="A list of events extracted from the conversation, that the user must have on his calendar.", - default=[], - ) - - def __str__(self): - result = (f"{str(self.title).capitalize()} ({str(self.category.value).capitalize()})\n" - f"{str(self.overview).capitalize()}\n") - - if self.action_items: - result += "Action Items:\n" - for item in self.action_items: - result += f"- {item.description}\n" - - if self.events: - result += "Events:\n" - for event in self.events: - result += f"- {event.title} ({event.start} - {event.duration} minutes)\n" - return result.strip() - - -class Geolocation(BaseModel): - google_place_id: Optional[str] = None - latitude: float - longitude: float - address: Optional[str] = None - location_type: Optional[str] = None - - -class MemorySource(str, Enum): - friend = 'friend' - omi = 'omi' - openglass = 'openglass' - screenpipe = 'screenpipe' - workflow = 'workflow' - sdcard = 'sdcard' - external_integration = 'external_integration' - - -class MemoryVisibility(str, Enum): - private = 'private' - shared = 'shared' - public = 'public' - - -class PostProcessingStatus(str, Enum): - not_started = 'not_started' - in_progress = 'in_progress' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - - -class MemoryStatus(str, Enum): - in_progress = 'in_progress' - processing = 'processing' - completed = 'completed' - failed = 'failed' - - -class PostProcessingModel(str, Enum): - fal_whisperx = 'fal_whisperx' - - -class MemoryPostProcessing(BaseModel): - status: PostProcessingStatus - model: PostProcessingModel - fail_reason: Optional[str] = None - - -class Memory(BaseModel): - id: str - created_at: datetime - started_at: Optional[datetime] - finished_at: Optional[datetime] - - source: Optional[MemorySource] = MemorySource.omi # TODO: once released migrate db to include this field - language: Optional[str] = None # applies only to Friend # TODO: once released migrate db to default 'en' - - structured: Structured - transcript_segments: List[TranscriptSegment] = [] - geolocation: Optional[Geolocation] = None - photos: List[MemoryPhoto] = [] - - plugins_results: List[PluginResult] = [] - - external_data: Optional[Dict] = None - app_id: Optional[str] = None - - discarded: bool = False - deleted: bool = False - visibility: MemoryVisibility = MemoryVisibility.private - - processing_memory_id: Optional[str] = None - status: Optional[MemoryStatus] = MemoryStatus.completed - - @staticmethod - def memories_to_string(memories: List['Memory'], use_transcript: bool = False) -> str: - result = [] - for i, memory in enumerate(memories): - if isinstance(memory, dict): - memory = Memory(**memory) - formatted_date = memory.created_at.astimezone(timezone.utc).strftime("%d %b %Y at %H:%M") + " UTC" - memory_str = (f"Memory #{i + 1}\n" - f"{formatted_date} ({str(memory.structured.category.value).capitalize()})\n" - f"{str(memory.structured.title).capitalize()}\n" - f"{str(memory.structured.overview).capitalize()}\n") - - if memory.structured.action_items: - memory_str += "Action Items:\n" - for item in memory.structured.action_items: - memory_str += f"- {item.description}\n" - - if memory.structured.events: - memory_str += "Events:\n" - for event in memory.structured.events: - memory_str += f"- {event.title} ({event.start} - {event.duration} minutes)\n" - - if use_transcript: - memory_str += (f"\nTranscript:\n{memory.get_transcript(include_timestamps=False)}\n") - - result.append(memory_str.strip()) - - return "\n\n---------------------\n\n".join(result).strip() - - def get_transcript(self, include_timestamps: bool) -> str: - # Warn: missing transcript for workflow source, external integration source - return TranscriptSegment.segments_as_string(self.transcript_segments, include_timestamps=include_timestamps) - - def as_dict_cleaned_dates(self): - memory_dict = self.dict() - memory_dict['structured']['events'] = [ - {**event, 'start': event['start'].isoformat()} for event in memory_dict['structured']['events'] - ] - memory_dict['created_at'] = memory_dict['created_at'].isoformat() - memory_dict['started_at'] = memory_dict['started_at'].isoformat() if memory_dict['started_at'] else None - memory_dict['finished_at'] = memory_dict['finished_at'].isoformat() if memory_dict['finished_at'] else None - return memory_dict - - -class CreateMemory(BaseModel): - started_at: datetime - finished_at: datetime - transcript_segments: List[TranscriptSegment] - geolocation: Optional[Geolocation] = None - - photos: List[MemoryPhoto] = [] - - source: MemorySource = MemorySource.omi - language: Optional[str] = None - - processing_memory_id: Optional[str] = None - - def get_transcript(self, include_timestamps: bool) -> str: - return TranscriptSegment.segments_as_string(self.transcript_segments, include_timestamps=include_timestamps) - - -class ExternalIntegrationMemorySource(str, Enum): - audio = 'audio_transcript' - message = 'message' - other = 'other_text' - - -class ExternalIntegrationCreateMemory(BaseModel): - started_at: Optional[datetime] = None - finished_at: Optional[datetime] = None - text: str - text_source: ExternalIntegrationMemorySource = ExternalIntegrationMemorySource.audio - text_source_spec: Optional[str] = None - geolocation: Optional[Geolocation] = None - - source: MemorySource = MemorySource.workflow - language: Optional[str] = None - - app_id: Optional[str] = None - - def get_transcript(self, include_timestamps: bool) -> str: - return self.text - - -class CreateMemoryResponse(BaseModel): - memory: Memory - messages: List[Message] = [] - - -class SetMemoryEventsStateRequest(BaseModel): - events_idx: List[int] - values: List[bool] - - -class SetMemoryActionItemsStateRequest(BaseModel): - items_idx: List[int] - values: List[bool] - - -class DeleteActionItemRequest(BaseModel): - description: str - completed: bool - - -class SearchRequest(BaseModel): - query: str - page: Optional[int] = 1 - per_page: Optional[int] = 10 - include_discarded: Optional[bool] = True diff --git a/backend/routers/apps.py b/backend/routers/apps.py index c3747bfda2..f62df94f6d 100644 --- a/backend/routers/apps.py +++ b/backend/routers/apps.py @@ -29,7 +29,7 @@ from utils.notifications import send_notification from utils.other import endpoints as auth from models.app import App, ActionType, AppCreate, AppUpdate -from utils.other.storage import upload_plugin_logo, delete_plugin_logo, upload_app_thumbnail, get_app_thumbnail_url +from utils.other.storage import upload_app_logo, delete_app_logo, upload_app_thumbnail, get_app_thumbnail_url from utils.social import get_twitter_profile, verify_latest_tweet, \ upsert_persona_from_twitter_profile, add_twitter_to_persona @@ -101,11 +101,11 @@ def create_app(app_data: str = Form(...), file: UploadFile = File(...), uid=Depe if action.get('action') not in [action_type.value for action_type in ActionType]: raise HTTPException(status_code=422, detail=f'Unsupported action type. Supported types: {", ".join([action_type.value for action_type in ActionType])}') - os.makedirs(f'_temp/plugins', exist_ok=True) - file_path = f"_temp/plugins/{file.filename}" + os.makedirs(f'_temp/apps', exist_ok=True) + file_path = f"_temp/apps/{file.filename}" with open(file_path, 'wb') as f: f.write(file.file.read()) - img_url = upload_plugin_logo(file_path, data['id']) + img_url = upload_app_logo(file_path, data['id']) data['image'] = img_url data['created_at'] = datetime.now(timezone.utc) @@ -155,11 +155,11 @@ async def create_persona(persona_data: str = Form(...), file: UploadFile = File( data['connected_accounts'] = ['omi'] data['persona_prompt'] = await generate_persona_prompt(uid, data) data['description'] = generate_persona_desc(uid, data['name']) - os.makedirs(f'_temp/plugins', exist_ok=True) - file_path = f"_temp/plugins/{file.filename}" + os.makedirs(f'_temp/apps', exist_ok=True) + file_path = f"_temp/apps/{file.filename}" with open(file_path, 'wb') as f: f.write(file.file.read()) - img_url = upload_plugin_logo(file_path, data['id']) + img_url = upload_app_logo(file_path, data['id']) data['image'] = img_url data['created_at'] = datetime.now(timezone.utc) @@ -187,12 +187,12 @@ async def update_persona(persona_id: str, persona_data: str = Form(...), file: U if file: if 'image' in persona and len(persona['image']) > 0 and \ persona['image'].startswith('https://storage.googleapis.com/'): - delete_plugin_logo(persona['image']) - os.makedirs(f'_temp/plugins', exist_ok=True) - file_path = f"_temp/plugins/{file.filename}" + delete_app_logo(persona['image']) + os.makedirs(f'_temp/apps', exist_ok=True) + file_path = f"_temp/apps/{file.filename}" with open(file_path, 'wb') as f: f.write(file.file.read()) - img_url = upload_plugin_logo(file_path, persona_id) + img_url = upload_app_logo(file_path, persona_id) data['image'] = img_url save_username(data['username'], uid) @@ -306,20 +306,20 @@ def generate_username(handle: str, uid: str = Depends(auth.get_current_user_uid) def update_app(app_id: str, app_data: str = Form(...), file: UploadFile = File(None), uid=Depends(auth.get_current_user_uid)): data = json.loads(app_data) - plugin = get_available_app_by_id(app_id, uid) - if not plugin: + app = get_available_app_by_id(app_id, uid) + if not app: raise HTTPException(status_code=404, detail='App not found') - if plugin['uid'] != uid: + if app['uid'] != uid: raise HTTPException(status_code=403, detail='You are not authorized to perform this action') if file: - if 'image' in plugin and len(plugin['image']) > 0 and \ - plugin['image'].startswith('https://storage.googleapis.com/'): - delete_plugin_logo(plugin['image']) - os.makedirs(f'_temp/plugins', exist_ok=True) - file_path = f"_temp/plugins/{file.filename}" + if 'image' in app and len(app['image']) > 0 and \ + app['image'].startswith('https://storage.googleapis.com/'): + delete_app_logo(app['image']) + os.makedirs(f'_temp/apps', exist_ok=True) + file_path = f"_temp/apps/{file.filename}" with open(file_path, 'wb') as f: f.write(file.file.read()) - img_url = upload_plugin_logo(file_path, app_id) + img_url = upload_app_logo(file_path, app_id) data['image'] = img_url data['updated_at'] = datetime.now(timezone.utc) @@ -341,9 +341,9 @@ def update_app(app_id: str, app_data: str = Form(...), file: UploadFile = File(N # payment link upsert_app_payment_link(data.get('id'), data.get('is_paid', False), data.get('price'), data.get('payment_plan'), data.get('uid'), - previous_price=plugin.get("price", 0)) + previous_price=app.get("price", 0)) - if plugin['approved'] and (plugin['private'] is None or plugin['private'] is False): + if app['approved'] and (app['private'] is None or app['private'] is False): delete_generic_cache('get_public_approved_apps_data') delete_app_cache_by_id(app_id) return {'status': 'ok'} @@ -351,13 +351,13 @@ def update_app(app_id: str, app_data: str = Form(...), file: UploadFile = File(N @router.delete('/v1/apps/{app_id}', tags=['v1']) def delete_app(app_id: str, uid: str = Depends(auth.get_current_user_uid)): - plugin = get_available_app_by_id(app_id, uid) - if not plugin: + app = get_available_app_by_id(app_id, uid) + if not app: raise HTTPException(status_code=404, detail='App not found') - if plugin['uid'] != uid: + if app['uid'] != uid: raise HTTPException(status_code=403, detail='You are not authorized to perform this action') delete_app_from_db(app_id) - if plugin['approved']: + if app['approved']: delete_generic_cache('get_public_approved_apps_data') delete_app_cache_by_id(app_id) return {'status': 'ok'} @@ -529,7 +529,7 @@ def get_notification_scopes(): @router.get('/v1/app-capabilities', tags=['v1']) -def get_plugin_capabilities(): +def get_app_capabilities(): return [ {'title': 'Chat', 'id': 'chat'}, {'title': 'Conversations', 'id': 'memories'}, @@ -852,7 +852,7 @@ def reject_app(app_id: str, uid: str, secret_key: str = Header(...)): app = get_available_app_by_id(app_id, uid) token = get_token_only(uid) if token: - # TODO: Add reason for rejection in payload and also redirect to the plugin page + # TODO: Add reason for rejection in payload and also redirect to the app page send_notification(token, 'App Rejected 😔', f'Your app {app["name"]} has been rejected. Please make the necessary changes and resubmit for approval.') return {'status': 'ok'} diff --git a/backend/routers/chat.py b/backend/routers/chat.py index 5b71339faf..62fa85732c 100644 --- a/backend/routers/chat.py +++ b/backend/routers/chat.py @@ -29,11 +29,11 @@ fc = FileChatTool() -def filter_messages(messages, plugin_id): - print('filter_messages', len(messages), plugin_id) +def filter_messages(messages, app_id): + print('filter_messages', len(messages), app_id) collected = [] for message in messages: - if message.sender == MessageSender.ai and message.plugin_id != plugin_id: + if message.sender == MessageSender.ai and message.plugin_id != app_id: break collected.append(message) print('filter_messages output:', len(collected)) @@ -41,7 +41,7 @@ def filter_messages(messages, plugin_id): def acquire_chat_session(uid: str, plugin_id: Optional[str] = None): - chat_session = chat_db.get_chat_session(uid, plugin_id=plugin_id) + chat_session = chat_db.get_chat_session(uid, app_id=plugin_id) if chat_session is None: cs = ChatSession( id=str(uuid.uuid4()), @@ -62,12 +62,12 @@ def send_message( plugin_id = None # get chat session - chat_session = chat_db.get_chat_session(uid, plugin_id=plugin_id) + chat_session = chat_db.get_chat_session(uid, app_id=plugin_id) chat_session = ChatSession(**chat_session) if chat_session else None message = Message( id=str(uuid.uuid4()), text=data.text, created_at=datetime.now(timezone.utc), sender='human', type='text', - plugin_id=plugin_id + app_id=plugin_id ) if data.file_ids is not None: new_file_ids = fc.retrieve_new_file(data.file_ids) @@ -94,7 +94,7 @@ def send_message( app_id = app.id if app else None - messages = list(reversed([Message(**msg) for msg in chat_db.get_messages(uid, limit=10, plugin_id=plugin_id)])) + messages = list(reversed([Message(**msg) for msg in chat_db.get_messages(uid, limit=10, app_id=plugin_id)])) def process_message(response: str, callback_data: dict): memories = callback_data.get('memories_found', []) @@ -122,7 +122,7 @@ def process_message(response: str, callback_data: dict): text=response, created_at=datetime.now(timezone.utc), sender='ai', - plugin_id=app_id, + app_id=app_id, type='text', memories_id=memories_id, ) @@ -181,7 +181,7 @@ def clear_chat_messages(app_id: Optional[str] = None, uid: str = Depends(auth.ge app_id = None # get current chat session - chat_session = chat_db.get_chat_session(uid, plugin_id=app_id) + chat_session = chat_db.get_chat_session(uid, app_id=app_id) chat_session_id = chat_session['id'] if chat_session else None err = chat_db.clear_chat(uid, app_id=app_id, chat_session_id=chat_session_id) @@ -205,7 +205,7 @@ def initial_message_util(uid: str, app_id: Optional[str] = None): # init chat session chat_session = acquire_chat_session(uid, plugin_id=app_id) - prev_messages = list(reversed(chat_db.get_messages(uid, limit=5, plugin_id=app_id))) + prev_messages = list(reversed(chat_db.get_messages(uid, limit=5, app_id=app_id))) print('initial_message_util returned', len(prev_messages), 'prev messages for', app_id) app = get_available_app_by_id(app_id, uid) @@ -228,7 +228,7 @@ def initial_message_util(uid: str, app_id: Optional[str] = None): text=text, created_at=datetime.now(timezone.utc), sender='ai', - plugin_id=app_id, + app_id=app_id, from_external_integration=False, type='text', memories_id=[], @@ -249,10 +249,10 @@ def get_messages(plugin_id: Optional[str] = None, uid: str = Depends(auth.get_cu if plugin_id in ['null', '']: plugin_id = None - chat_session = chat_db.get_chat_session(uid, plugin_id=plugin_id) + chat_session = chat_db.get_chat_session(uid, app_id=plugin_id) chat_session_id = chat_session['id'] if chat_session else None - messages = chat_db.get_messages(uid, limit=100, include_conversations=True, plugin_id=plugin_id, + messages = chat_db.get_messages(uid, limit=100, include_conversations=True, app_id=plugin_id, chat_session_id=chat_session_id) print('get_messages', len(messages), plugin_id) if not messages: @@ -455,7 +455,7 @@ def clear_chat_messages(plugin_id: Optional[str] = None, uid: str = Depends(auth plugin_id = None # get current chat session - chat_session = chat_db.get_chat_session(uid, plugin_id=plugin_id) + chat_session = chat_db.get_chat_session(uid, app_id=plugin_id) chat_session_id = chat_session['id'] if chat_session else None err = chat_db.clear_chat(uid, app_id=plugin_id, chat_session_id=chat_session_id) diff --git a/backend/routers/integration.py b/backend/routers/integration.py index 0f24654e7d..ff8ae90e3a 100644 --- a/backend/routers/integration.py +++ b/backend/routers/integration.py @@ -12,7 +12,7 @@ from utils.apps import verify_api_key import database.redis_db as redis_db import database.memories as memory_db -from database.redis_db import get_enabled_plugins, r as redis_client +from database.redis_db import get_enabled_apps, r as redis_client import database.notifications as notification_db import models.integrations as integration_models import models.conversation as conversation_models @@ -86,7 +86,7 @@ async def create_conversation_via_integration( raise HTTPException(status_code=404, detail="App not found") # Verify if the uid has enabled the app - enabled_plugins = redis_db.get_enabled_plugins(uid) + enabled_plugins = redis_db.get_enabled_apps(uid) if app_id not in enabled_plugins: raise HTTPException(status_code=403, detail="App is not enabled for this user") @@ -153,7 +153,7 @@ async def create_memories_via_integration( raise HTTPException(status_code=404, detail="App not found") # Verify if the uid has enabled the app - enabled_plugins = redis_db.get_enabled_plugins(uid) + enabled_plugins = redis_db.get_enabled_apps(uid) if app_id not in enabled_plugins: raise HTTPException(status_code=403, detail="App is not enabled for this user") @@ -196,7 +196,7 @@ async def create_facts_via_integration( raise HTTPException(status_code=404, detail="App not found") # Verify if the uid has enabled the app - enabled_plugins = redis_db.get_enabled_plugins(uid) + enabled_plugins = redis_db.get_enabled_apps(uid) if app_id not in enabled_plugins: raise HTTPException(status_code=403, detail="App is not enabled for this user") @@ -243,7 +243,7 @@ async def get_memories_via_integration( raise HTTPException(status_code=404, detail="App not found") # Verify if the uid has enabled the app - enabled_plugins = redis_db.get_enabled_plugins(uid) + enabled_plugins = redis_db.get_enabled_apps(uid) if app_id not in enabled_plugins: raise HTTPException(status_code=403, detail="App is not enabled for this user") @@ -294,7 +294,7 @@ async def get_conversations_via_integration( raise HTTPException(status_code=404, detail="App not found") # Verify if the uid has enabled the app - enabled_plugins = redis_db.get_enabled_plugins(uid) + enabled_plugins = redis_db.get_enabled_apps(uid) if app_id not in enabled_plugins: raise HTTPException(status_code=403, detail="App is not enabled for this user") @@ -375,7 +375,7 @@ async def search_conversations_via_integration( raise HTTPException(status_code=404, detail="App not found") # Verify if the uid has enabled the app - enabled_plugins = redis_db.get_enabled_plugins(uid) + enabled_plugins = redis_db.get_enabled_apps(uid) if app_id not in enabled_plugins: raise HTTPException(status_code=403, detail="App is not enabled for this user") @@ -464,7 +464,7 @@ async def send_notification_via_integration( app = App(**app_data) # Check if user has app installed - user_enabled = set(get_enabled_plugins(uid)) + user_enabled = set(get_enabled_apps(uid)) if app_id not in user_enabled: raise HTTPException(status_code=403, detail='User does not have this app installed') diff --git a/backend/routers/mcp.py b/backend/routers/mcp.py index 982c3a688c..91e9e34052 100644 --- a/backend/routers/mcp.py +++ b/backend/routers/mcp.py @@ -11,7 +11,7 @@ # from database.redis_db import get_filter_category_items # from database.vector_db import query_vectors_by_metadata from models.memories import MemoryDB, Memory, MemoryCategory -from models.memory import CategoryEnum +from models.conversation import CategoryEnum from utils.apps import update_personas_async from firebase_admin import auth diff --git a/backend/routers/notifications.py b/backend/routers/notifications.py index 563cbdfa30..f9c4c0d03f 100644 --- a/backend/routers/notifications.py +++ b/backend/routers/notifications.py @@ -5,7 +5,7 @@ from fastapi.responses import JSONResponse from typing import Tuple, Optional -from database.redis_db import get_enabled_plugins, r as redis_client +from database.redis_db import get_enabled_apps, r as redis_client from utils.apps import get_available_app_by_id, verify_api_key from utils.app_integrations import send_app_notification import database.notifications as notification_db @@ -105,7 +105,7 @@ def send_app_notification_to_user( app = App(**app_data) # Check if user has app installed - user_enabled = set(get_enabled_plugins(uid)) + user_enabled = set(get_enabled_apps(uid)) if data['aid'] not in user_enabled: raise HTTPException(status_code=403, detail='User does not have this app installed') diff --git a/backend/scripts/users/plugins_count.py b/backend/scripts/users/apps_count.py similarity index 94% rename from backend/scripts/users/plugins_count.py rename to backend/scripts/users/apps_count.py index 4fea7bfa26..dd782418f0 100644 --- a/backend/scripts/users/plugins_count.py +++ b/backend/scripts/users/apps_count.py @@ -29,14 +29,14 @@ # noinspection PyUnresolvedReferences from models.conversation import Conversation -from database.redis_db import get_enabled_plugins, set_plugin_installs_count +from database.redis_db import get_enabled_apps, set_app_installs_count from database._client import get_users_uid import database.conversations as conversations_db import database.chat as chat_db def single(uid, data): - pids = get_enabled_plugins(uid) + pids = get_enabled_apps(uid) for pid in pids: data[pid] += 1 return pids @@ -58,7 +58,7 @@ def execute(): print(json.dumps(data, indent=2)) for pid, count in data.items(): - set_plugin_installs_count(pid, count) + set_app_installs_count(pid, count) def count_memory_prompt_plugins_trigger(): diff --git a/backend/utils/agent.py b/backend/utils/agent.py index 0f9da5acd2..55115e9428 100644 --- a/backend/utils/agent.py +++ b/backend/utils/agent.py @@ -10,7 +10,6 @@ from utils.retrieval.graph import AsyncStreamingCallback from openai.types.responses import ResponseTextDeltaEvent - # omi_documentation: dict = get_github_docs_content() # omi_documentation_str = "\n\n".join( # [f"{k}:\n {v}" for k, v in omi_documentation.items()] @@ -25,12 +24,12 @@ async def run( - mcp_server: MCPServer, - uid: str, - messages: List[Message], - respond: callable, - plugin: Optional[App] = None, - stream_callback: Optional[AsyncStreamingCallback] = None, + mcp_server: MCPServer, + uid: str, + messages: List[Message], + respond: callable, + plugin: Optional[App] = None, + stream_callback: Optional[AsyncStreamingCallback] = None, ): docs_agent = Agent( name="Omi Documentation Agent", @@ -62,30 +61,30 @@ async def run( async for event in result.stream_events(): if event.type == "raw_response_event" and isinstance( - event.data, ResponseTextDeltaEvent + event.data, ResponseTextDeltaEvent ): if stream_callback: # Remove "data: " prefix if present delta = event.data.delta if isinstance(delta, str) and delta.startswith("data: "): - delta = delta[len("data: ") :] + delta = delta[len("data: "):] await stream_callback.put_data(delta) async def execute_agent_chat_stream( - uid: str, - messages: List[Message], - plugin: Optional[App] = None, - cited: Optional[bool] = False, - callback_data: dict = {}, - chat_session: Optional[ChatSession] = None, + uid: str, + messages: List[Message], + app: Optional[App] = None, + cited: Optional[bool] = False, + callback_data: dict = {}, + chat_session: Optional[ChatSession] = None, ) -> AsyncGenerator[str, None]: - print("execute_agent_chat_stream plugin: ", plugin.id if plugin else "") + print("execute_agent_chat_stream app: ", app.id if app else "") callback = AsyncStreamingCallback() async with MCPServerStdio( - cache_tools_list=True, - params={"command": "uvx", "args": ["mcp-server-omi", "-v"]}, + cache_tools_list=True, + params={"command": "uvx", "args": ["mcp-server-omi", "-v"]}, ) as server: task = asyncio.create_task( run( @@ -93,7 +92,7 @@ async def execute_agent_chat_stream( uid, messages, lambda x: callback_data.update({"answer": x}), - plugin, + app, callback, ) ) @@ -105,7 +104,7 @@ async def execute_agent_chat_stream( if chunk: # Remove "data: " prefix if present if isinstance(chunk, str) and chunk.startswith("data: "): - chunk = chunk[len("data: ") :] + chunk = chunk[len("data: "):] yield chunk else: break @@ -123,8 +122,8 @@ async def execute_agent_chat_stream( async def send_single_message(): async with MCPServerStdio( - cache_tools_list=True, - params={"command": "uvx", "args": ["mcp-server-omi"]}, + cache_tools_list=True, + params={"command": "uvx", "args": ["mcp-server-omi"]}, ) as server: with trace(workflow_name="Omi Agent"): await run( @@ -138,8 +137,8 @@ async def send_single_message(): async def interactive_chat_stream(): print("Starting interactive chat with Omi Agent. Type 'exit' to quit.") async with MCPServerStdio( - cache_tools_list=True, - params={"command": "uvx", "args": ["mcp-server-omi", "-v"]}, + cache_tools_list=True, + params={"command": "uvx", "args": ["mcp-server-omi", "-v"]}, ) as server: while True: user_input = input("\nYou: ") @@ -196,12 +195,14 @@ async def interactive_chat_stream(): ), ] + async def main(): async for chunk in execute_agent_chat_stream( - uid="viUv7GtdoHXbK1UBCDlPuTDuPgJ2", messages=messages + uid="viUv7GtdoHXbK1UBCDlPuTDuPgJ2", messages=messages ): if chunk: print(chunk, end="", flush=True) print() # for newline after stream ends + asyncio.run(main()) diff --git a/backend/utils/app_integrations.py b/backend/utils/app_integrations.py index f00252105a..b95662c497 100644 --- a/backend/utils/app_integrations.py +++ b/backend/utils/app_integrations.py @@ -8,7 +8,7 @@ from database import mem_db from database import redis_db from database.apps import record_app_usage -from database.chat import add_plugin_message, get_plugin_messages +from database.chat import add_app_message, get_app_messages from database.redis_db import get_generic_cache, set_generic_cache from models.app import App, UsageHistoryType from models.chat import Message @@ -131,7 +131,7 @@ def _single(app: App): for key, message in results.items(): if not message: continue - messages.append(add_plugin_message(message, key, uid, conversation.id)) + messages.append(add_app_message(message, key, uid, conversation.id)) return messages @@ -172,18 +172,18 @@ def _retrieve_contextual_memories(uid: str, user_context): return conversations_db.get_conversations_by_id(uid, memories_id) -def _hit_proactive_notification_rate_limits(uid: str, plugin: App): - sent_at = mem_db.get_proactive_noti_sent_at(uid, plugin.id) +def _hit_proactive_notification_rate_limits(uid: str, app: App): + sent_at = mem_db.get_proactive_noti_sent_at(uid, app.id) if sent_at and time.time() - sent_at < PROACTIVE_NOTI_LIMIT_SECONDS: return True # remote - sent_at = redis_db.get_proactive_noti_sent_at(uid, plugin.id) + sent_at = redis_db.get_proactive_noti_sent_at(uid, app.id) if not sent_at: return False - ttl = redis_db.get_proactive_noti_sent_at_ttl(uid, plugin.id) + ttl = redis_db.get_proactive_noti_sent_at_ttl(uid, app.id) if ttl > 0: - mem_db.set_proactive_noti_sent_at(uid, plugin.id, int(time.time() + ttl), ttl=ttl) + mem_db.set_proactive_noti_sent_at(uid, app.id, int(time.time() + ttl), ttl=ttl) return time.time() - sent_at < PROACTIVE_NOTI_LIMIT_SECONDS @@ -194,14 +194,14 @@ def _set_proactive_noti_sent_at(uid: str, app: App): redis_db.set_proactive_noti_sent_at(uid, app.id, int(ts), ttl=PROACTIVE_NOTI_LIMIT_SECONDS) -def _process_proactive_notification(uid: str, token: str, plugin: App, data): - if not plugin.has_capability("proactive_notification") or not data: - print(f"Plugins {plugin.id} is not proactive_notification or data invalid", uid) +def _process_proactive_notification(uid: str, token: str, app: App, data): + if not app.has_capability("proactive_notification") or not data: + print(f"App {app.id} is not proactive_notification or data invalid", uid) return None # rate limits - if _hit_proactive_notification_rate_limits(uid, plugin): - print(f"Plugins {plugin.id} is reach rate limits 1 noti per user per {PROACTIVE_NOTI_LIMIT_SECONDS}s", uid) + if _hit_proactive_notification_rate_limits(uid, app): + print(f"App {app.id} is reach rate limits 1 noti per user per {PROACTIVE_NOTI_LIMIT_SECONDS}s", uid) return None max_prompt_char_limit = 128000 @@ -209,12 +209,12 @@ def _process_proactive_notification(uid: str, token: str, plugin: App, data): prompt = data.get('prompt', '') if len(prompt) > max_prompt_char_limit: - send_app_notification(token, plugin.name, plugin.id, + send_app_notification(token, app.name, app.id, f"Prompt too long: {len(prompt)}/{max_prompt_char_limit} characters. Please shorten.") - print(f"Plugin {plugin.id}, prompt too long, length: {len(prompt)}/{max_prompt_char_limit}", uid) + print(f"App {app.id}, prompt too long, length: {len(prompt)}/{max_prompt_char_limit}", uid) return None - filter_scopes = plugin.filter_proactive_notification_scopes(data.get('params', [])) + filter_scopes = app.filter_proactive_notification_scopes(data.get('params', [])) # context context = None @@ -226,21 +226,21 @@ def _process_proactive_notification(uid: str, token: str, plugin: App, data): # messages messages = [] if 'user_chat' in filter_scopes: - messages = list(reversed([Message(**msg) for msg in get_plugin_messages(uid, plugin.id, limit=10)])) + messages = list(reversed([Message(**msg) for msg in get_app_messages(uid, app.id, limit=10)])) # print(f'_process_proactive_notification context {context[:100] if context else "empty"}') # retrive message message = get_proactive_message(uid, prompt, filter_scopes, context, messages) if not message or len(message) < min_message_char_limit: - print(f"Plugins {plugin.id}, message too short", uid) + print(f"Plugins {app.id}, message too short", uid) return None # send notification - send_app_notification(token, plugin.name, plugin.id, message) + send_app_notification(token, app.name, app.id, message) # set rate - _set_proactive_noti_sent_at(uid, plugin) + _set_proactive_noti_sent_at(uid, app) return message @@ -342,7 +342,7 @@ def _single(app: App): for key, message in results.items(): if not message: continue - messages.append(add_plugin_message(message, key, uid)) + messages.append(add_app_message(message, key, uid)) return messages @@ -350,7 +350,7 @@ def _single(app: App): def send_app_notification(token: str, app_name: str, app_id: str, message: str): ai_message = NotificationMessage( text=message, - plugin_id=app_id, + app_id=app_id, from_integration='true', type='text', notification_type='plugin', diff --git a/backend/utils/apps.py b/backend/utils/apps.py index 9ab46348a8..11e96207e9 100644 --- a/backend/utils/apps.py +++ b/backend/utils/apps.py @@ -16,9 +16,9 @@ from database.auth import get_user_name from database.conversations import get_conversations from database.memories import get_memories, get_user_public_memories -from database.redis_db import get_enabled_plugins, get_plugin_reviews, get_generic_cache, \ +from database.redis_db import get_enabled_apps, get_app_reviews, get_generic_cache, \ set_generic_cache, set_app_usage_history_cache, get_app_usage_history_cache, get_app_money_made_cache, \ - set_app_money_made_cache, get_plugins_installs_count, get_plugins_reviews, get_app_cache_by_id, set_app_cache_by_id, \ + set_app_money_made_cache, get_apps_installs_count, get_apps_reviews, get_app_cache_by_id, set_app_cache_by_id, \ set_app_review_cache, get_app_usage_count_cache, set_app_money_made_amount_cache, get_app_money_made_amount_cache, \ set_app_usage_count_cache, set_user_paid_app, get_user_paid_app, delete_app_cache_by_id, is_username_taken from database.users import get_stripe_connect_account_id @@ -62,11 +62,11 @@ def remove_app_access_for_tester(app_id: str, uid: str): # ******************************** -def weighted_rating(plugin): - C = 3.0 # Assume 3.0 is the mean rating across all plugins +def weighted_rating(app): + C = 3.0 # Assume 3.0 is the mean rating across all apps m = 5 # Minimum number of ratings required to be considered - R = plugin.rating_avg or 0 - v = plugin.rating_count or 0 + R = app.rating_avg or 0 + v = app.rating_count or 0 return (v / (v + m) * R) + (m / (v + m) * C) @@ -81,14 +81,14 @@ def get_popular_apps() -> List[App]: set_generic_cache('get_popular_apps_data', popular_apps, 60 * 30) # 30 minutes cached app_ids = [app['id'] for app in popular_apps] - plugins_install = get_plugins_installs_count(app_ids) - plugins_review = get_plugins_reviews(app_ids) + apps_install = get_apps_installs_count(app_ids) + apps_reviews = get_apps_reviews(app_ids) apps = [] for app in popular_apps: app_dict = app - app_dict['installs'] = plugins_install.get(app['id'], 0) - reviews = plugins_review.get(app['id'], {}) + app_dict['installs'] = apps_install.get(app['id'], 0) + reviews = apps_reviews.get(app['id'], {}) sorted_reviews = reviews.values() rating_avg = sum([x['score'] for x in sorted_reviews]) / len(sorted_reviews) if reviews else None app_dict['rating_avg'] = rating_avg @@ -119,21 +119,21 @@ def get_available_apps(uid: str, include_reviews: bool = False) -> List[App]: set_generic_cache('get_public_approved_apps_data', public_approved_data, 60 * 10) # 10 minutes cached if tester: tester_apps = get_apps_for_tester_db(uid) - user_enabled = set(get_enabled_plugins(uid)) + user_enabled = set(get_enabled_apps(uid)) all_apps = private_data + public_approved_data + public_unapproved_data + tester_apps apps = [] app_ids = [app['id'] for app in all_apps] - plugins_install = get_plugins_installs_count(app_ids) - plugins_review = get_plugins_reviews(app_ids) if include_reviews else {} + apps_install = get_apps_installs_count(app_ids) + apps_review = get_apps_reviews(app_ids) if include_reviews else {} for app in all_apps: app_dict = app app_dict['enabled'] = app['id'] in user_enabled app_dict['rejected'] = app['approved'] is False - app_dict['installs'] = plugins_install.get(app['id'], 0) + app_dict['installs'] = apps_install.get(app['id'], 0) if include_reviews: - reviews = plugins_review.get(app['id'], {}) + reviews = apps_review.get(app['id'], {}) sorted_reviews = reviews.values() rating_avg = sum([x['score'] for x in sorted_reviews]) / len(sorted_reviews) if reviews else None app_dict['reviews'] = [details for details in reviews.values() if details['review']] @@ -170,7 +170,7 @@ def get_available_app_by_id_with_reviews(app_id: str, uid: str | None) -> dict | return None app['money_made'] = get_app_money_made_amount(app['id']) if not app['private'] else None app['usage_count'] = get_app_usage_count(app['id']) if not app['private'] else None - reviews = get_plugin_reviews(app['id']) + reviews = get_app_reviews(app['id']) sorted_reviews = reviews.values() rating_avg = sum([x['score'] for x in sorted_reviews]) / len(sorted_reviews) if reviews else None app['reviews'] = [details for details in reviews.values() if details['review']] @@ -179,12 +179,12 @@ def get_available_app_by_id_with_reviews(app_id: str, uid: str | None) -> dict | app['user_review'] = reviews.get(uid) # enabled - user_enabled = set(get_enabled_plugins(uid)) + user_enabled = set(get_enabled_apps(uid)) app['enabled'] = app['id'] in user_enabled # install - plugins_install = get_plugins_installs_count([app['id']]) - app['installs'] = plugins_install.get(app['id'], 0) + apps_install = get_apps_installs_count([app['id']]) + app['installs'] = apps_install.get(app['id'], 0) return app @@ -209,15 +209,15 @@ def get_approved_available_apps(include_reviews: bool = False) -> list[App]: set_generic_cache('get_public_approved_apps_data', all_apps, 60 * 10) # 10 minutes cached app_ids = [app['id'] for app in all_apps] - plugins_install = get_plugins_installs_count(app_ids) - plugins_review = get_plugins_reviews(app_ids) if include_reviews else {} + apps_installs = get_apps_installs_count(app_ids) + apps_reviews = get_apps_reviews(app_ids) if include_reviews else {} apps = [] for app in all_apps: app_dict = app - app_dict['installs'] = plugins_install.get(app['id'], 0) + app_dict['installs'] = apps_installs.get(app['id'], 0) if include_reviews: - reviews = plugins_review.get(app['id'], {}) + reviews = apps_reviews.get(app['id'], {}) sorted_reviews = reviews.values() rating_avg = sum([x['score'] for x in sorted_reviews]) / len(sorted_reviews) if reviews else None app_dict['reviews'] = [] @@ -235,10 +235,6 @@ def set_app_review(app_id: str, uid: str, review: dict): return {'status': 'ok'} -def get_app_reviews(app_id: str) -> dict: - return get_plugin_reviews(app_id) - - def get_app_usage_count(app_id: str) -> int: cached_count = get_app_usage_count_cache(app_id) if cached_count: @@ -381,7 +377,7 @@ def paid_app(app_id: str, uid: str): def is_audio_bytes_app_enabled(uid: str): - enabled_apps = get_enabled_plugins(uid) + enabled_apps = get_enabled_apps(uid) # https://firebase.google.com/docs/firestore/query-data/queries#in_and_array-contains-any limit = 30 enabled_apps = list(set(enabled_apps)) @@ -670,5 +666,5 @@ def app_can_create_conversation(app: dict) -> bool: def is_user_app_enabled(uid: str, app_id: str) -> bool: """Check if a specific app is enabled for the user based on Redis cache.""" - user_enabled_apps = set(get_enabled_plugins(uid)) + user_enabled_apps = set(get_enabled_apps(uid)) return app_id in user_enabled_apps diff --git a/backend/utils/chat.py b/backend/utils/chat.py index 0f7b02ac9a..d0b14c936e 100644 --- a/backend/utils/chat.py +++ b/backend/utils/chat.py @@ -69,11 +69,11 @@ def delete_file(): chat_db.add_message(uid, message.dict()) # not support plugin - plugin = None - plugin_id = None + app = None + app_id = None messages = list(reversed([Message(**msg) for msg in chat_db.get_messages(uid, limit=10)])) - response, ask_for_nps, memories = execute_graph_chat(uid, messages, plugin) # plugin + response, ask_for_nps, memories = execute_graph_chat(uid, messages, app) # app memories_id = [] # check if the items in the conversations list are dict if memories: @@ -89,14 +89,14 @@ def delete_file(): text=response, created_at=datetime.now(timezone.utc), sender='ai', - plugin_id=plugin_id, + app_id=app_id, type='text', memories_id=memories_id, ) chat_db.add_message(uid, ai_message.dict()) ai_message.memories = memories if len(memories) < 5 else memories[:5] - if plugin_id: - record_app_usage(uid, plugin_id, UsageHistoryType.chat_message_sent, message_id=ai_message.id) + if app_id: + record_app_usage(uid, app_id, UsageHistoryType.chat_message_sent, message_id=ai_message.id) ai_message_resp = ai_message.dict() @@ -139,8 +139,8 @@ def delete_file(): yield f"message: {mdata}\n\n" # not support plugin - plugin = None - plugin_id = None + app = None + app_id = None def process_message(response: str, callback_data: dict): memories = callback_data.get('memories_found', []) @@ -160,21 +160,21 @@ def process_message(response: str, callback_data: dict): text=response, created_at=datetime.now(timezone.utc), sender='ai', - plugin_id=plugin_id, + app_id=app_id, type='text', memories_id=memories_id, ) chat_db.add_message(uid, ai_message.dict()) ai_message.memories = [MessageConversation(**m) for m in (memories if len(memories) < 5 else memories[:5])] - if plugin_id: - record_app_usage(uid, plugin_id, UsageHistoryType.chat_message_sent, message_id=ai_message.id) + if app_id: + record_app_usage(uid, app_id, UsageHistoryType.chat_message_sent, message_id=ai_message.id) return ai_message, ask_for_nps messages = list(reversed([Message(**msg) for msg in chat_db.get_messages(uid, limit=10)])) callback_data = {} - async for chunk in execute_graph_chat_stream(uid, messages, plugin, cited=False, callback_data=callback_data): + async for chunk in execute_graph_chat_stream(uid, messages, app, cited=False, callback_data=callback_data): if chunk: data = chunk.replace("\n", "__CRLF__") yield f'{data}\n\n' @@ -196,14 +196,14 @@ def process_message(response: str, callback_data: dict): return -def send_chat_message_notification(token: str, plugin_name: str, plugin_id: str, message: str, message_id: str): +def send_chat_message_notification(token: str, app_name: str, app_id: str, message: str, message_id: str): ai_message = NotificationMessage( id=message_id, text=message, - plugin_id=plugin_id, + plugin_id=app_id, from_integration='true', type='text', notification_type='plugin', - navigate_to=f'/chat/{plugin_id}', + navigate_to=f'/chat/{app_id}', ) - send_notification(token, plugin_name + ' says', message, NotificationMessage.get_message_as_dict(ai_message)) + send_notification(token, app_name + ' says', message, NotificationMessage.get_message_as_dict(ai_message)) diff --git a/backend/utils/conversations/process_conversation.py b/backend/utils/conversations/process_conversation.py index f613ec4006..d5c34d2f34 100644 --- a/backend/utils/conversations/process_conversation.py +++ b/backend/utils/conversations/process_conversation.py @@ -161,7 +161,7 @@ def _trigger_apps(uid: str, conversation: Conversation, is_reprocess: bool = Fal print(f"Selected best app for conversation: {best_app.name}") # enabled - user_enabled = set(redis_db.get_enabled_plugins(uid)) + user_enabled = set(redis_db.get_enabled_apps(uid)) if best_app.id not in user_enabled: redis_db.enable_app(uid, best_app.id) diff --git a/backend/utils/other/storage.py b/backend/utils/other/storage.py index c046ca5d00..f19d02a752 100644 --- a/backend/utils/other/storage.py +++ b/backend/utils/other/storage.py @@ -20,7 +20,7 @@ postprocessing_audio_bucket = os.getenv('BUCKET_POSTPROCESSING') memories_recordings_bucket = os.getenv('BUCKET_MEMORIES_RECORDINGS') syncing_local_bucket = os.getenv('BUCKET_TEMPORAL_SYNC_LOCAL') -omi_plugins_bucket = os.getenv('BUCKET_PLUGINS_LOGOS') +omi_apps_bucket = os.getenv('BUCKET_PLUGINS_LOGOS') app_thumbnails_bucket = os.getenv('BUCKET_APP_THUMBNAILS') chat_files_bucket = os.getenv('BUCKET_CHAT_FILES') @@ -239,19 +239,19 @@ def _get_signed_url(blob, minutes): return signed_url -def upload_plugin_logo(file_path: str, plugin_id: str): - bucket = storage_client.bucket(omi_plugins_bucket) - path = f'{plugin_id}.png' +def upload_app_logo(file_path: str, app_id: str): + bucket = storage_client.bucket(omi_apps_bucket) + path = f'{app_id}.png' blob = bucket.blob(path) blob.cache_control = 'public, no-cache' blob.upload_from_filename(file_path) - return f'https://storage.googleapis.com/{omi_plugins_bucket}/{path}' + return f'https://storage.googleapis.com/{omi_apps_bucket}/{path}' -def delete_plugin_logo(img_url: str): - bucket = storage_client.bucket(omi_plugins_bucket) - path = img_url.split(f'https://storage.googleapis.com/{omi_plugins_bucket}/')[1] - print('delete_plugin_logo', path) +def delete_app_logo(img_url: str): + bucket = storage_client.bucket(omi_apps_bucket) + path = img_url.split(f'https://storage.googleapis.com/{omi_apps_bucket}/')[1] + print('delete_app_logo', path) blob = bucket.blob(path) blob.delete() diff --git a/backend/utils/retrieval/graph.py b/backend/utils/retrieval/graph.py index 04431d28cb..dda7a394f2 100644 --- a/backend/utils/retrieval/graph.py +++ b/backend/utils/retrieval/graph.py @@ -419,27 +419,27 @@ def file_chat_question(state: GraphState): @timeit def execute_graph_chat( - uid: str, messages: List[Message], plugin: Optional[App] = None, cited: Optional[bool] = False + uid: str, messages: List[Message], app: Optional[App] = None, cited: Optional[bool] = False ) -> Tuple[str, bool, List[Conversation]]: - print('execute_graph_chat plugin :', plugin.id if plugin else '') + print('execute_graph_chat app :', app.id if app else '') tz = notification_db.get_user_time_zone(uid) result = graph.invoke( - {"uid": uid, "tz": tz, "cited": cited, "messages": messages, "plugin_selected": plugin}, + {"uid": uid, "tz": tz, "cited": cited, "messages": messages, "plugin_selected": app}, {"configurable": {"thread_id": str(uuid.uuid4())}}, ) return result.get("answer"), result.get('ask_for_nps', False), result.get("memories_found", []) async def execute_graph_chat_stream( - uid: str, messages: List[Message], plugin: Optional[App] = None, cited: Optional[bool] = False, + uid: str, messages: List[Message], app: Optional[App] = None, cited: Optional[bool] = False, callback_data: dict = {}, chat_session: Optional[ChatSession] = None ) -> AsyncGenerator[str, None]: - print('execute_graph_chat_stream plugin: ', plugin.id if plugin else '') + print('execute_graph_chat_stream app: ', app.id if app else '') tz = notification_db.get_user_time_zone(uid) callback = AsyncStreamingCallback() task = asyncio.create_task(graph_stream.ainvoke( - {"uid": uid, "tz": tz, "cited": cited, "messages": messages, "plugin_selected": plugin, + {"uid": uid, "tz": tz, "cited": cited, "messages": messages, "plugin_selected": app, "streaming": True, "callback": callback, "chat_session": chat_session, }, {"configurable": {"thread_id": str(uuid.uuid4())}}, ))