import logging from typing import Dict, Any, Optional, List, Union from datetime import datetime, UTC import json from modules.interfaces.interfaceAppModel import User from modules.interfaces.interfaceChatModel import ( TaskStatus, ChatDocument, TaskItem, TaskAction, TaskResult, ChatStat, ChatLog, ChatMessage, ChatWorkflow ) from modules.workflow.serviceContainer import ServiceContainer from modules.interfaces.interfaceChatObjects import ChatObjects logger = logging.getLogger(__name__) class ChatManager: """Chat manager with improved AI integration and method handling""" def __init__(self, currentUser: User, chatInterface: ChatObjects): self.currentUser = currentUser self.chatInterface = chatInterface self.service: ServiceContainer = None self.workflow: ChatWorkflow = None # ===== Initialization and Setup ===== async def initialize(self, workflow: ChatWorkflow) -> None: """Initialize chat manager with workflow""" self.workflow = workflow self.service = ServiceContainer(self.currentUser, self.workflow) def _extractJsonFromResponse(self, response: str) -> Optional[Dict[str, Any]]: """Extract JSON from verbose AI response that may contain explanatory text""" try: # First try direct JSON parsing return json.loads(response) except json.JSONDecodeError: # Try to find JSON in the response import re # Look for JSON object patterns json_patterns = [ r'\{.*\}', # Basic JSON object r'\[\{.*\}\]', # JSON array of objects ] for pattern in json_patterns: matches = re.findall(pattern, response, re.DOTALL) for match in matches: try: return json.loads(match) except json.JSONDecodeError: continue # If no JSON found, log the full response for debugging logger.error(f"Could not extract JSON from response: {response[:500]}...") return None # ===== Task Creation and Management ===== async def createInitialTask(self, workflow: ChatWorkflow, initialMessage: ChatMessage) -> Optional[TaskItem]: """Create the initial task from the first message""" try: logger.info(f"Creating initial task for workflow {workflow.id}") # Create task definition prompt prompt = await self._createTaskDefinitionPrompt(initialMessage.message, workflow) # Get AI response response = await self.service.callAiTextAdvanced(prompt) # Parse response taskDef = self._extractJsonFromResponse(response) # Validate task definition if not taskDef: logger.error("Could not extract valid JSON from AI response") return None if not isinstance(taskDef, dict): logger.error("Task definition must be a JSON object") return None requiredFields = ["status", "feedback", "actions"] for field in requiredFields: if field not in taskDef: logger.error(f"Missing required field: {field}") return None if not isinstance(taskDef["actions"], list): logger.error("Actions must be a list") return None logger.info(f"Task definition validated: {len(taskDef['actions'])} actions") # Create task using interface taskData = { "workflowId": workflow.id, "userInput": initialMessage.message, "status": taskDef["status"], "feedback": taskDef["feedback"], "actionList": [] } # Add actions for actionDef in taskDef["actions"]: if not isinstance(actionDef, dict): continue requiredFields = ["method", "action", "parameters"] if not all(field in actionDef for field in requiredFields): continue # Create action using interface actionData = { "execMethod": actionDef["method"], "execAction": actionDef["action"], "execParameters": actionDef["parameters"], "execResultLabel": actionDef.get("resultLabel") } action = self.chatInterface.createTaskAction(actionData) if action: # Convert TaskAction object to dictionary for database storage actionDict = { "id": action.id, "execMethod": action.execMethod, "execAction": action.execAction, "execParameters": action.execParameters, "execResultLabel": action.execResultLabel, "status": action.status, "error": action.error, "retryCount": action.retryCount, "retryMax": action.retryMax, "processingTime": action.processingTime, "timestamp": action.timestamp.isoformat() if action.timestamp else None, "result": action.result, "resultDocuments": action.resultDocuments } taskData["actionList"].append(actionDict) # Create task using interface task = self.chatInterface.createTask(taskData) if task: logger.info(f"Task created successfully: {task.id}") else: logger.error("Failed to create task") return task except Exception as e: logger.error(f"Error creating initial task: {str(e)}") return None async def createNextTask(self, workflow: ChatWorkflow, previousResult: TaskResult) -> Optional[TaskItem]: """Create next task based on previous result""" try: logger.info(f"Creating next task for workflow {workflow.id}") # Check if previous result was successful if not previousResult.success: logger.error(f"Previous task failed: {previousResult.error}") return None # Create task definition prompt prompt = await self._createTaskDefinitionPrompt(previousResult.feedback, workflow) # Get AI response response = await self.service.callAiTextAdvanced(prompt) # Parse response taskDef = self._extractJsonFromResponse(response) # Validate task definition if not taskDef: logger.error("Could not extract valid JSON from AI response") return None if not isinstance(taskDef, dict): logger.error("Task definition must be a JSON object") return None requiredFields = ["status", "feedback", "actions"] for field in requiredFields: if field not in taskDef: logger.error(f"Missing required field: {field}") return None if not isinstance(taskDef["actions"], list): logger.error("Actions must be a list") return None logger.info(f"Next task definition validated: {len(taskDef['actions'])} actions") # Create task using interface taskData = { "workflowId": workflow.id, "userInput": previousResult.feedback, "status": taskDef["status"], "feedback": taskDef["feedback"], "actionList": [] } # Add actions for actionDef in taskDef["actions"]: if not isinstance(actionDef, dict): continue requiredFields = ["method", "action", "parameters"] if not all(field in actionDef for field in requiredFields): continue # Create action using interface actionData = { "execMethod": actionDef["method"], "execAction": actionDef["action"], "execParameters": actionDef["parameters"], "execResultLabel": actionDef.get("resultLabel") } action = self.chatInterface.createTaskAction(actionData) if action: # Convert TaskAction object to dictionary for database storage actionDict = { "id": action.id, "execMethod": action.execMethod, "execAction": action.execAction, "execParameters": action.execParameters, "execResultLabel": action.execResultLabel, "status": action.status, "error": action.error, "retryCount": action.retryCount, "retryMax": action.retryMax, "processingTime": action.processingTime, "timestamp": action.timestamp.isoformat() if action.timestamp else None, "result": action.result, "resultDocuments": action.resultDocuments } taskData["actionList"].append(actionDict) # Create task using interface task = self.chatInterface.createTask(taskData) if task: logger.info(f"Next task created successfully: {task.id}") else: logger.error("Failed to create next task") return task except Exception as e: logger.error(f"Error creating next task: {str(e)}") return None async def executeTask(self, task: TaskItem) -> TaskItem: """Execute a task's actions""" try: # Execute each action for action in task.actionList: # Create action prompt prompt = f"""Execute the following action: Action: {action.execMethod}.{action.execAction} Parameters: {json.dumps(action.execParameters)} Please provide a JSON response with: 1. result: The result of the action 2. resultLabel: A label for the result (format: documentList__