import logging from typing import Dict, Any, List, Optional from modules.datamodels.datamodelUam import User, UserConnection from modules.datamodels.datamodelChat import ChatDocument, ChatMessage, ChatStat, ChatLog from modules.datamodels.datamodelAi import AiCallOptions, OperationTypeEnum, PriorityEnum, ProcessingModeEnum from modules.security.tokenManager import TokenManager from modules.shared.progressLogger import ProgressLogger logger = logging.getLogger(__name__) class ChatService: """Service class containing methods for document processing, chat operations, and workflow management""" def __init__(self, serviceCenter): self.services = serviceCenter self.user = serviceCenter.user # self.services.workflow is now the ChatWorkflow object (stable during workflow execution) self.interfaceDbChat = serviceCenter.interfaceDbChat self.interfaceDbComponent = serviceCenter.interfaceDbComponent self.interfaceDbApp = serviceCenter.interfaceDbApp self._progressLogger = None def getChatDocumentsFromDocumentList(self, documentList) -> List[ChatDocument]: """Get ChatDocuments from a DocumentReferenceList. Args: documentList: DocumentReferenceList (required) Returns: List[ChatDocument]: List of ChatDocument objects """ from modules.datamodels.datamodelDocref import DocumentReferenceList if not isinstance(documentList, DocumentReferenceList): logger.error(f"getChatDocumentsFromDocumentList: Invalid documentList type: {type(documentList)}. Expected DocumentReferenceList.") return [] # Convert to string list for processing stringRefs = documentList.to_string_list() try: # Use self.services.workflow which is the ChatWorkflow object (stable during workflow execution) workflow = self.services.workflow if not workflow: logger.error("getChatDocumentsFromDocumentList: No workflow available (self.services.workflow is not set)") return [] workflowId = workflow.id if hasattr(workflow, 'id') else 'NO_ID' workflowObjId = id(workflow) logger.debug(f"getChatDocumentsFromDocumentList: input documentList = {stringRefs}") logger.debug(f"getChatDocumentsFromDocumentList: using workflow.id = {workflowId}, workflow object id = {workflowObjId}") # Root cause analysis: Verify workflow.messages integrity and detect workflow changes self._verifyWorkflowMessagesIntegrity(workflow, workflowId) # Debug: list available messages with their labels and document names (filtered by workflowId) try: if workflow and hasattr(workflow, 'messages') and workflow.messages: msgLines = [] messagesFromOtherWorkflows = [] for message in workflow.messages: msgWorkflowId = getattr(message, 'workflowId', None) # Only include messages that belong to this workflow if msgWorkflowId and msgWorkflowId != workflowId: messagesFromOtherWorkflows.append(f"id={getattr(message, 'id', None)}, label={getattr(message, 'documentsLabel', None)}, workflowId={msgWorkflowId}") continue # Also skip messages without workflowId (shouldn't happen, but be safe) if not msgWorkflowId: messagesFromOtherWorkflows.append(f"id={getattr(message, 'id', None)}, label={getattr(message, 'documentsLabel', None)}, workflowId=Missing") continue label = getattr(message, 'documentsLabel', None) docNames = [] if getattr(message, 'documents', None): for doc in message.documents: name = getattr(doc, 'fileName', None) or getattr(doc, 'documentName', None) or 'Unnamed' docNames.append(name) msgLines.append( f"- id={getattr(message, 'id', None)}, label={label}, workflowId={msgWorkflowId}, docs={docNames}" ) if msgLines: logger.debug("getChatDocumentsFromDocumentList: available messages (filtered for workflow):\n" + "\n".join(msgLines)) if messagesFromOtherWorkflows: logger.warning(f"getChatDocumentsFromDocumentList: Found {len(messagesFromOtherWorkflows)} messages from other workflows in workflow.messages list:\n" + "\n".join(messagesFromOtherWorkflows)) else: logger.debug("getChatDocumentsFromDocumentList: no messages available on current workflow") except Exception as e: logger.debug(f"getChatDocumentsFromDocumentList: unable to enumerate messages for debug: {e}") allDocuments = [] for docRef in stringRefs: if docRef.startswith("docItem:"): # docItem:: - extract ID and find document parts = docRef.split(':') if len(parts) >= 2: docId = parts[1] # Find the document by ID for message in workflow.messages: # Validate message belongs to this workflow msgWorkflowId = getattr(message, 'workflowId', None) if not msgWorkflowId or msgWorkflowId != workflowId: continue if message.documents: for doc in message.documents: if doc.id == docId: docName = getattr(doc, 'fileName', 'unknown') allDocuments.append(doc) break elif docRef.startswith("docList:"): # docList::