diff --git a/modules/workflow/managerChat.py b/modules/workflow/managerChat.py index 275a5302..f68c8888 100644 --- a/modules/workflow/managerChat.py +++ b/modules/workflow/managerChat.py @@ -728,6 +728,10 @@ NOTE: Respond with ONLY the JSON object. Do not include any explanatory text.""" action.setSuccess() action.result = result.data.get("result", "") action.execResultLabel = result.data.get("resultLabel", "") + + # Create and store message in workflow for successful action + await self._createActionMessage(action, result, workflow) + else: action.setError(result.error or "Action execution failed") @@ -749,6 +753,143 @@ NOTE: Respond with ONLY the JSON object. Do not include any explanatory text.""" "action": action } + async def _createActionMessage(self, action: TaskAction, result: Any, workflow: ChatWorkflow) -> None: + """Create and store a message for the action result in the workflow""" + try: + # Get result data + result_data = result.data if hasattr(result, 'data') else {} + result_label = result_data.get("resultLabel", "") + documents_data = result_data.get("documents", []) + + # Create message data + message_data = { + "workflowId": workflow.id, + "role": "assistant", + "message": f"Executed {action.execMethod}.{action.execAction} successfully", + "status": "step", + "sequenceNr": len(workflow.messages) + 1, + "publishedAt": datetime.now(UTC).isoformat(), + "actionId": action.id, + "actionMethod": action.execMethod, + "actionName": action.execAction, + "documentsLabel": result_label, # Use resultLabel as documentsLabel + "documents": [] + } + + # Process documents if any + if documents_data: + processed_documents = [] + for doc_data in documents_data: + try: + # Extract document information + document_name = doc_data.get("documentName", f"{action.execMethod}_{action.execAction}_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}") + document_data = doc_data.get("documentData", {}) + + # Determine file extension and MIME type + file_extension = self._getFileExtension(document_name) + mime_type = self._getMimeType(file_extension) + + # Convert document data to string content + content = self._convertDocumentDataToString(document_data, file_extension) + + # Create file in database + file_id = self.service.createFile( + fileName=document_name, + mimeType=mime_type, + content=content, + base64encoded=False + ) + + # Create ChatDocument object + document = self.service.createDocument( + fileName=document_name, + mimeType=mime_type, + content=content, + base64encoded=False + ) + + if document: + processed_documents.append(document) + logger.info(f"Created document: {document_name} with file ID: {file_id}") + + except Exception as e: + logger.error(f"Error processing document {doc_data.get('documentName', 'unknown')}: {str(e)}") + continue + + # Update message with processed documents + message_data["documents"] = processed_documents + + # Create message using interface + message = self.chatInterface.createWorkflowMessage(message_data) + if message: + workflow.messages.append(message) + logger.info(f"Created action message for {action.execMethod}.{action.execAction} with {len(message_data.get('documents', []))} documents") + else: + logger.error(f"Failed to create workflow message for action {action.execMethod}.{action.execAction}") + + except Exception as e: + logger.error(f"Error creating action message: {str(e)}") + + def _getFileExtension(self, filename: str) -> str: + """Extract file extension from filename""" + if '.' in filename: + return filename.split('.')[-1].lower() + return "txt" # Default to text + + def _getMimeType(self, extension: str) -> str: + """Get MIME type based on file extension""" + mime_types = { + 'txt': 'text/plain', + 'json': 'application/json', + 'xml': 'application/xml', + 'csv': 'text/csv', + 'html': 'text/html', + 'md': 'text/markdown', + 'py': 'text/x-python', + 'js': 'application/javascript', + 'css': 'text/css', + 'pdf': 'application/pdf', + 'doc': 'application/msword', + 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xls': 'application/vnd.ms-excel', + 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'ppt': 'application/vnd.ms-powerpoint', + 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation' + } + return mime_types.get(extension, 'application/octet-stream') + + def _convertDocumentDataToString(self, document_data: Dict[str, Any], file_extension: str) -> str: + """Convert document data to string content based on file type""" + try: + if file_extension == 'json': + return json.dumps(document_data, indent=2, ensure_ascii=False) + elif file_extension in ['txt', 'md', 'html', 'css', 'js', 'py']: + # For text files, try to extract text content + if isinstance(document_data, dict): + # Look for common text content fields + text_fields = ['content', 'text', 'data', 'result', 'summary'] + for field in text_fields: + if field in document_data: + content = document_data[field] + if isinstance(content, str): + return content + elif isinstance(content, (dict, list)): + return json.dumps(content, indent=2, ensure_ascii=False) + + # If no text field found, convert entire dict to JSON + return json.dumps(document_data, indent=2, ensure_ascii=False) + elif isinstance(document_data, str): + return document_data + else: + return str(document_data) + else: + # For other file types, convert to JSON + return json.dumps(document_data, indent=2, ensure_ascii=False) + + except Exception as e: + logger.error(f"Error converting document data to string: {str(e)}") + return str(document_data) + async def _performTaskReview(self, review_context: Dict[str, Any]) -> Dict[str, Any]: """Perform AI-based task review""" try: @@ -778,11 +919,23 @@ NOTE: Respond with ONLY the JSON object. Do not include any explanatory text.""" } def _getPreviousResultsFromActions(self, task_actions: List[TaskAction]) -> List[str]: - """Get list of previous results from completed actions""" + """Get list of previous results from completed actions and workflow messages""" results = [] + + # Get results from action objects for action in task_actions: if action.execResultLabel and action.isSuccessful(): results.append(action.execResultLabel) + + # Get results from workflow messages (for actions that have been executed) + if hasattr(self, 'workflow') and self.workflow and self.workflow.messages: + for message in self.workflow.messages: + if (message.role == 'assistant' and + message.status == 'step' and + message.documentsLabel and + message.documentsLabel not in results): + results.append(message.documentsLabel) + return results def _calculateTaskQualityMetrics(self, task_step: Dict[str, Any], action_results: List[Dict[str, Any]]) -> Dict[str, Any]: diff --git a/modules/workflow/serviceContainer.py b/modules/workflow/serviceContainer.py index 5e1a78b3..09bd9538 100644 --- a/modules/workflow/serviceContainer.py +++ b/modules/workflow/serviceContainer.py @@ -224,7 +224,7 @@ class ServiceContainer: if message.documentsLabel.startswith("mdoc:"): return message.documentsLabel - # Otherwise construct the reference + # Otherwise construct the reference using the action ID and documents label return f"mdoc:{message.actionId}:{message.documentsLabel}" def getChatDocumentsFromDocumentReference(self, documentReference: str) -> List[ChatDocument]: @@ -256,6 +256,14 @@ class ServiceContainer: message.documentsLabel == documentReference and # Compare full reference message.documents): return message.documents + + # If not found by actionId, try to find by documentsLabel only + # This handles cases where the reference format might be different + for message in self.workflow.messages: + if (message.documentsLabel and + message.documentsLabel == documentReference and + message.documents): + return message.documents return [] diff --git a/notes/changelog.txt b/notes/changelog.txt index f9ac8d39..bd7b6ae4 100644 --- a/notes/changelog.txt +++ b/notes/changelog.txt @@ -34,7 +34,6 @@ LOGIC TODO methods: - reultLabel not to generate in the function, but to be set according to the action definition -- align documentList objects: chat document to have prefix "cdoc", message document list to have prefix "mdoc" (globally: instead of document and documentList) - after action execution to store the documents in db and in a message object