# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ Chat Playground Feature Routes. Implements the endpoints for chat playground workflow management as a feature. """ import logging from typing import Optional, Dict, Any from fastapi import APIRouter, HTTPException, Depends, Body, Path, Query, Request # Import auth modules from modules.auth import limiter, getRequestContext, RequestContext # Import interfaces from modules.interfaces import interfaceDbChat # Import models from modules.datamodels.datamodelChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum # Import workflow control functions from modules.workflows.automation import chatStart, chatStop # Configure logger logger = logging.getLogger(__name__) # Create router for chat playground feature endpoints router = APIRouter( prefix="/api/chatplayground", tags=["Chat Playground Feature"], responses={404: {"description": "Not found"}} ) def _getServiceChat(context: RequestContext, featureInstanceId: str = None): """Get chat interface with feature instance context.""" return interfaceDbChat.getInterface( context.user, mandateId=str(context.mandateId) if context.mandateId else None, featureInstanceId=featureInstanceId ) async def _validateInstanceAccess(instanceId: str, context: RequestContext) -> str: """ Validate that user has access to the feature instance. Args: instanceId: Feature instance ID context: Request context Returns: mandateId for the instance Raises: HTTPException if access is denied """ from modules.interfaces.interfaceDbApp import getRootInterface rootInterface = getRootInterface() # Get feature instance (Pydantic model) instance = rootInterface.getFeatureInstance(instanceId) if not instance: raise HTTPException(status_code=404, detail=f"Feature instance {instanceId} not found") # Check user has access to this instance using interface method featureAccess = rootInterface.getFeatureAccess(str(context.user.id), instanceId) if not featureAccess or not featureAccess.enabled: raise HTTPException(status_code=403, detail="Access denied to this feature instance") return str(instance.mandateId) if instance.mandateId else None # Workflow start endpoint @router.post("/{instanceId}/start", response_model=ChatWorkflow) @limiter.limit("120/minute") async def start_workflow( request: Request, instanceId: str = Path(..., description="Feature instance ID"), workflowId: Optional[str] = Query(None, description="Optional ID of the workflow to continue"), workflowMode: WorkflowModeEnum = Query(..., description="Workflow mode: 'Dynamic' or 'Automation' (mandatory)"), userInput: UserInputRequest = Body(...), context: RequestContext = Depends(getRequestContext) ) -> ChatWorkflow: """ Starts a new workflow or continues an existing one. Args: instanceId: Feature instance ID workflowMode: "Dynamic" for iterative dynamic-style processing, "Automation" for automated workflow execution """ try: # Validate access and get mandate ID mandateId = await _validateInstanceAccess(instanceId, context) # Start or continue workflow workflow = await chatStart( context.user, userInput, workflowMode, workflowId, mandateId=mandateId, featureInstanceId=instanceId ) return workflow except HTTPException: raise except Exception as e: logger.error(f"Error in start_workflow: {str(e)}") raise HTTPException( status_code=500, detail=str(e) ) # Stop workflow endpoint @router.post("/{instanceId}/{workflowId}/stop", response_model=ChatWorkflow) @limiter.limit("120/minute") async def stop_workflow( request: Request, instanceId: str = Path(..., description="Feature instance ID"), workflowId: str = Path(..., description="ID of the workflow to stop"), context: RequestContext = Depends(getRequestContext) ) -> ChatWorkflow: """Stops a running workflow.""" try: # Validate access and get mandate ID mandateId = await _validateInstanceAccess(instanceId, context) # Stop workflow (pass featureInstanceId for proper RBAC filtering) workflow = await chatStop( context.user, workflowId, mandateId=mandateId, featureInstanceId=instanceId ) return workflow except HTTPException: raise except Exception as e: logger.error(f"Error in stop_workflow: {str(e)}") raise HTTPException( status_code=500, detail=str(e) ) # Unified Chat Data Endpoint for Polling @router.get("/{instanceId}/{workflowId}/chatData") @limiter.limit("120/minute") async def get_workflow_chat_data( request: Request, instanceId: str = Path(..., description="Feature instance ID"), workflowId: str = Path(..., description="ID of the workflow"), afterTimestamp: Optional[float] = Query(None, description="Unix timestamp to get data after"), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """ Get unified chat data (messages, logs, stats) for a workflow with timestamp-based selective data transfer. Returns all data types in chronological order based on _createdAt timestamp. """ try: # Validate access await _validateInstanceAccess(instanceId, context) # Get service with feature instance context chatInterface = _getServiceChat(context, featureInstanceId=instanceId) # Verify workflow exists workflow = chatInterface.getWorkflow(workflowId) if not workflow: raise HTTPException( status_code=404, detail=f"Workflow with ID {workflowId} not found" ) # Get unified chat data chatData = chatInterface.getUnifiedChatData(workflowId, afterTimestamp) return chatData except HTTPException: raise except Exception as e: logger.error(f"Error getting unified chat data: {str(e)}", exc_info=True) raise HTTPException( status_code=500, detail=f"Error getting unified chat data: {str(e)}" ) # Get workflows for this instance @router.get("/{instanceId}/workflows") @limiter.limit("120/minute") async def get_workflows( request: Request, instanceId: str = Path(..., description="Feature instance ID"), page: int = Query(1, ge=1, description="Page number"), pageSize: int = Query(20, ge=1, le=100, description="Items per page"), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """ Get all workflows for this feature instance. """ try: # Validate access await _validateInstanceAccess(instanceId, context) # Get service with feature instance context chatInterface = _getServiceChat(context, featureInstanceId=instanceId) # Get workflows with pagination from modules.datamodels.datamodelPagination import PaginationParams pagination = PaginationParams(page=page, pageSize=pageSize) result = chatInterface.getWorkflows(pagination=pagination) return result except HTTPException: raise except Exception as e: logger.error(f"Error getting workflows: {str(e)}", exc_info=True) raise HTTPException( status_code=500, detail=f"Error getting workflows: {str(e)}" )