import logging import importlib import pkgutil import inspect from typing import Dict, Any, List, Optional from modules.interfaces.serviceAppClass import User from modules.interfaces.serviceChatModel import ( TaskStatus, ChatDocument, TaskItem, TaskAction, TaskResult, ChatStat, ChatLog, ChatMessage, ChatWorkflow, UserConnection ) from modules.interfaces.interfaceAi import interfaceAi from modules.interfaces.serviceChatClass import getInterface as getChatInterface from modules.interfaces.serviceManagementClass import getInterface as getFileInterface from modules.workflow.managerDocument import DocumentManager from modules.methods.methodBase import MethodBase import uuid import base64 logger = logging.getLogger(__name__) class ServiceContainer: """Service container that provides access to all services and their functions""" def __init__(self, currentUser: User, workflow: ChatWorkflow): # Core services self.user = currentUser self.workflow = workflow self.tasks = workflow.tasks self.statusEnums = TaskStatus self.currentTask = None # Initialize current task as None # Initialize managers self.interfaceChat = getChatInterface(currentUser) self.interfaceFiles = getFileInterface(currentUser) self.interfaceAi = interfaceAi() self.documentManager = DocumentManager(self) # Initialize methods catalog self.methods = None # Discover additional methods self._discoverMethods() def _discoverMethods(self): """Dynamically discover all method classes in modules.methods package""" try: # Import the methods package methodsPackage = importlib.import_module('modules.methods') # Discover all modules in the package for _, name, isPkg in pkgutil.iter_modules(methodsPackage.__path__): if not isPkg and name.startswith('method'): try: # Import the module module = importlib.import_module(f'modules.methods.{name}') # Find all classes in the module that inherit from MethodBase for itemName, item in inspect.getmembers(module): if (inspect.isclass(item) and issubclass(item, MethodBase) and item != MethodBase): # Instantiate the method and add to service methodInstance = item() self.methods[methodInstance.name] = methodInstance logger.info(f"Discovered method: {methodInstance.name}") except Exception as e: logger.error(f"Error loading method module {name}: {str(e)}") except Exception as e: logger.error(f"Error discovering methods: {str(e)}") # ===== Functions ===== def extractContent(self, prompt: str, document: ChatDocument) -> str: """Extract content from document using prompt""" return self.documentManager.extractContent(prompt, document) def getMethodsCatalog(self) -> Dict[str, Any]: """Get catalog of available methods""" return self.methods def getMethodsList(self) -> List[str]: """Get list of available methods with their signatures""" methodList = [] for methodName, method in self.methods.items(): for actionName, action in method.actions.items(): # Get parameter types and return type from action signature paramTypes = [] for paramName, param in action.parameters.items(): paramTypes.append(f"{paramName}:{param.type}") # Format: method.action([param1:type, param2:type])->resultLabel:type # description signature = f"{methodName}.{actionName}([{', '.join(paramTypes)}])->{action.resultLabel}:{action.resultType}" if action.description: signature += f" # {action.description}" methodList.append(signature) return methodList def getDocumentReferenceList(self) -> Dict[str, List[Dict[str, str]]]: """Get list of document references sorted by datetime, categorized by chat round""" chat_refs = [] history_refs = [] # Process messages in reverse order to find current chat round for message in reversed(self.workflow.messages): # Get document references from message if message.documents: # For messages with action context, use documentList reference if message.actionId and message.documentsLabel: doc_ref = self.getDocumentReferenceFromMessage(message) doc_info = { "documentReference": doc_ref, "datetime": message.publishedAt } # Add to appropriate list based on message status if message.status == "first": chat_refs.append(doc_info) break # Stop after finding first message elif message.status == "step": chat_refs.append(doc_info) else: history_refs.append(doc_info) # For regular messages, use individual document references else: for doc in message.documents: doc_ref = self.getDocumentReferenceFromChatDocument(doc) doc_info = { "documentReference": doc_ref, "datetime": message.publishedAt } # Add to appropriate list based on message status if message.status == "first": chat_refs.append(doc_info) break # Stop after finding first message elif message.status == "step": chat_refs.append(doc_info) else: history_refs.append(doc_info) # Stop processing if we hit a first message if message.status == "first": break # Sort both lists by datetime in descending order chat_refs.sort(key=lambda x: x["datetime"], reverse=True) history_refs.sort(key=lambda x: x["datetime"], reverse=True) return { "chat": chat_refs, "history": history_refs } def getDocumentReferenceFromChatDocument(self, document: ChatDocument) -> str: """Get document reference from ChatDocument""" return f"document_{document.id}_{document.filename}" def getDocumentReferenceFromMessage(self, message: ChatMessage) -> str: """Get document reference from ChatMessage with action context""" if not message.actionId or not message.documentsLabel: return None # If documentsLabel already contains the full reference format, return it if message.documentsLabel.startswith("documentList_"): return message.documentsLabel # Otherwise construct the reference return f"documentList_{message.actionId}_{message.documentsLabel}" def getChatDocumentsFromDocumentReference(self, documentReference: str) -> List[ChatDocument]: """Get ChatDocuments from document reference""" try: # Parse reference format parts = documentReference.split('_', 2) # Split into max 3 parts if len(parts) < 3: return [] ref_type = parts[0] ref_id = parts[1] ref_label = parts[2] # Keep the full label if ref_type == "document": # Handle ChatDocument reference: document__ # Find document in workflow messages for message in self.workflow.messages: if message.documents: for doc in message.documents: if doc.id == ref_id: return [doc] elif ref_type == "documentList": # Handle document list reference: documentList__