From 55fed49a28d23f861f70dd329c307b71e75dac2f Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Tue, 8 Jul 2025 01:21:27 +0200
Subject: [PATCH] ready for handover test
---
modules/workflow/managerChat.py | 155 ++++++++++++++++++++++++++-
modules/workflow/serviceContainer.py | 10 +-
notes/changelog.txt | 1 -
3 files changed, 163 insertions(+), 3 deletions(-)
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