371 lines
No EOL
12 KiB
Python
371 lines
No EOL
12 KiB
Python
import os
|
|
import json
|
|
from fastapi import APIRouter, HTTPException, Depends, Body, Path, Query
|
|
from typing import List, Dict, Any, Optional
|
|
from fastapi import status
|
|
import asyncio
|
|
import uuid
|
|
from datetime import datetime
|
|
import logging
|
|
from dataclasses import dataclass
|
|
|
|
# Import interfaces
|
|
from modules.lucydom_interface import get_lucydom_interface
|
|
from modules.auth import get_current_active_user, get_user_context
|
|
from modules.chat import get_chat_manager
|
|
|
|
# Import models
|
|
import modules.lucydom_model as lucydom_model
|
|
|
|
# Configure logger
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Create router for workflow endpoints
|
|
router = APIRouter(
|
|
prefix="/api/workflows",
|
|
tags=["Workflow"],
|
|
responses={404: {"description": "Not found"}}
|
|
)
|
|
|
|
@dataclass
|
|
class AppContext:
|
|
"""Context object for all required connections and user information"""
|
|
mandate_id: int
|
|
user_id: int
|
|
interface_data: Any # LucyDOM Interface
|
|
interface_chat: Any # Chat Manager
|
|
|
|
async def get_context(current_user: Dict[str, Any]) -> AppContext:
|
|
"""
|
|
Creates a central context object with all required interfaces
|
|
|
|
Args:
|
|
current_user: Current user from authentication
|
|
|
|
Returns:
|
|
AppContext object with all required connections
|
|
"""
|
|
mandate_id, user_id = await get_user_context(current_user)
|
|
interface_data = get_lucydom_interface(mandate_id, user_id)
|
|
interface_chat = get_chat_manager(mandate_id, user_id)
|
|
|
|
return AppContext(
|
|
mandate_id=mandate_id,
|
|
user_id=user_id,
|
|
interface_data=interface_data,
|
|
interface_chat=interface_chat
|
|
)
|
|
|
|
|
|
@router.get("", response_model=List[Dict[str, Any]])
|
|
async def list_workflows(current_user: Dict[str, Any] = Depends(get_current_active_user)):
|
|
"""Lists all workflows of the user"""
|
|
context = await get_context(current_user)
|
|
|
|
# Retrieve workflows for the user
|
|
workflows = context.interface_data.get_workflows_by_user(context.user_id)
|
|
|
|
return workflows
|
|
|
|
@router.post("/{workflow_id}/user-input", response_model=Dict[str, Any])
|
|
async def submit_user_input(
|
|
workflow_id: str = Path(..., description="ID of the workflow (optional)"),
|
|
user_input: lucydom_model.UserInputRequest = Body(...),
|
|
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
|
):
|
|
"""
|
|
Allows the user to send inputs for a running workflow
|
|
or start a new workflow.
|
|
"""
|
|
logger.debug("CHECK 01...")
|
|
context = await get_context(current_user)
|
|
logger.debug("CHECK 02...")
|
|
|
|
# Improved logging
|
|
logger.info(f"User input for workflow '{workflow_id}' received")
|
|
|
|
if workflow_id == "new": workflow_id = None #agreed placeholder with frontend
|
|
logger.debug("CHECK trying...")
|
|
try:
|
|
# Continue or start workflow with the chat manager
|
|
user_input_dict = {
|
|
"prompt": user_input.prompt,
|
|
"list_file_id": user_input.list_file_id
|
|
}
|
|
logger.debug("CHECK start Workflow ...")
|
|
workflow = await context.interface_chat.chat_run(user_input_dict, workflow_id)
|
|
logger.debug("CHECK end Workflow ...")
|
|
|
|
if not workflow:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error processing user input"
|
|
)
|
|
|
|
return {
|
|
"workflow_id": workflow.get("id"),
|
|
"status": "running",
|
|
"message": "User input received and being processed"
|
|
}
|
|
except HTTPException:
|
|
# Forward HTTP exceptions
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error processing user input: {str(e)}", exc_info=True)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Error processing user input: {str(e)}"
|
|
)
|
|
|
|
@router.post("/{workflow_id}/stop", response_model=Dict[str, Any])
|
|
async def stop_workflow(
|
|
workflow_id: str = Path(..., description="ID of the workflow to stop"),
|
|
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
|
):
|
|
"""Stops a running workflow"""
|
|
context = await get_context(current_user)
|
|
|
|
# Load workflow
|
|
workflow = context.interface_data.get_workflow(workflow_id)
|
|
if not workflow:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Workflow with ID {workflow_id} not found"
|
|
)
|
|
|
|
# Set status to "stopped"
|
|
workflow["status"] = "completed"
|
|
workflow["last_activity"] = datetime.now().isoformat()
|
|
|
|
# Update workflow
|
|
context.interface_data.update_workflow(workflow_id, workflow)
|
|
|
|
return {
|
|
"workflow_id": workflow_id,
|
|
"status": "completed",
|
|
"message": "Workflow has been stopped"
|
|
}
|
|
|
|
@router.delete("/{workflow_id}", response_model=Dict[str, Any])
|
|
async def delete_workflow(
|
|
workflow_id: str,
|
|
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
|
):
|
|
"""Deletes a workflow"""
|
|
context = await get_context(current_user)
|
|
|
|
# Delete workflow
|
|
success = context.interface_data.delete_workflow(workflow_id)
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Workflow with ID {workflow_id} not found"
|
|
)
|
|
|
|
return {
|
|
"workflow_id": workflow_id,
|
|
"message": "Workflow has been deleted"
|
|
}
|
|
|
|
@router.get("/{workflow_id}/data-statistics", response_model=Dict[str, Any])
|
|
async def get_workflow_data_statistics(
|
|
workflow_id: str,
|
|
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
|
):
|
|
"""
|
|
Returns statistics about the transferred data volumes for a workflow.
|
|
"""
|
|
context = await get_context(current_user)
|
|
|
|
# Load workflow
|
|
workflow = context.interface_data.get_workflow(workflow_id)
|
|
if not workflow:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Workflow with ID {workflow_id} not found"
|
|
)
|
|
|
|
# Return data statistics
|
|
data_stats = workflow.get("data_stats", {})
|
|
if not data_stats:
|
|
data_stats = {
|
|
"total_processing_time": 0.0,
|
|
"total_token_count": 0,
|
|
"total_bytes_sent": 0,
|
|
"total_bytes_received": 0
|
|
}
|
|
|
|
return {
|
|
"workflow_id": workflow_id,
|
|
"data_stats": data_stats
|
|
}
|
|
|
|
@router.get("/{workflow_id}/status", response_model=Dict[str, Any])
|
|
async def get_workflow_status(
|
|
workflow_id: str,
|
|
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
|
):
|
|
"""Get the status of a workflow"""
|
|
context = await get_context(current_user)
|
|
|
|
# Load workflow
|
|
workflow = context.interface_data.get_workflow(workflow_id)
|
|
if not workflow:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Workflow with ID {workflow_id} not found"
|
|
)
|
|
|
|
# Create status from the loaded workflow
|
|
status_info = {
|
|
"id": workflow.get("id"),
|
|
"name": workflow.get("name"),
|
|
"status": workflow.get("status"),
|
|
"started_at": workflow.get("started_at"),
|
|
"last_activity": workflow.get("last_activity"),
|
|
"data_stats": workflow.get("data_stats", {})
|
|
}
|
|
|
|
return status_info
|
|
|
|
@router.get("/{workflow_id}/logs", response_model=List[Dict[str, Any]])
|
|
async def get_workflow_logs(
|
|
workflow_id: str,
|
|
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
|
):
|
|
"""Get logs of a workflow"""
|
|
context = await get_context(current_user)
|
|
|
|
# Get logs
|
|
logs = context.interface_data.get_workflow_logs(workflow_id)
|
|
if not logs:
|
|
# Check if the workflow exists
|
|
workflow = context.interface_data.get_workflow(workflow_id)
|
|
if not workflow:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Workflow with ID {workflow_id} not found"
|
|
)
|
|
# Return empty log list
|
|
logs = []
|
|
|
|
return logs
|
|
|
|
@router.get("/{workflow_id}/messages", response_model=List[Dict[str, Any]])
|
|
async def get_workflow_messages(
|
|
workflow_id: str,
|
|
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
|
):
|
|
"""Get messages of a workflow"""
|
|
context = await get_context(current_user)
|
|
|
|
# Get messages
|
|
messages = context.interface_data.get_workflow_messages(workflow_id)
|
|
if messages is None:
|
|
# Check if the workflow exists
|
|
workflow = context.interface_data.get_workflow(workflow_id)
|
|
if not workflow:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Workflow with ID {workflow_id} not found"
|
|
)
|
|
# Return empty message list
|
|
messages = []
|
|
|
|
return messages
|
|
|
|
@router.delete("/{workflow_id}/messages/{message_id}", response_model=Dict[str, Any])
|
|
async def delete_workflow_message(
|
|
workflow_id: str = Path(..., description="ID of the workflow"),
|
|
message_id: str = Path(..., description="ID of the message to delete"),
|
|
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
|
):
|
|
"""
|
|
Deletes a single message from a workflow.
|
|
|
|
This function removes the message from the workflow and also from the database.
|
|
"""
|
|
context = await get_context(current_user)
|
|
|
|
try:
|
|
# Check if the workflow exists
|
|
workflow = context.interface_data.get_workflow(workflow_id)
|
|
if not workflow:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Workflow with ID {workflow_id} not found"
|
|
)
|
|
|
|
# Delete message
|
|
success = context.interface_data.delete_workflow_message(workflow_id, message_id)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Message with ID {message_id} in workflow {workflow_id} not found"
|
|
)
|
|
|
|
return {
|
|
"workflow_id": workflow_id,
|
|
"message_id": message_id,
|
|
"success": True,
|
|
"message": "Message successfully deleted"
|
|
}
|
|
|
|
except HTTPException:
|
|
# Forward known HTTP exceptions
|
|
raise
|
|
except Exception as e:
|
|
# Catch other errors
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Error deleting the message: {str(e)}"
|
|
)
|
|
|
|
@router.delete("/{workflow_id}/messages/{message_id}/files/{file_id}", response_model=Dict[str, Any])
|
|
async def delete_file_from_message(
|
|
workflow_id: str = Path(..., description="ID of the workflow"),
|
|
message_id: str = Path(..., description="ID of the message"),
|
|
file_id: str = Path(..., description="ID of the file to delete"),
|
|
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
|
):
|
|
"""
|
|
Deletes a single file reference from a message in the workflow.
|
|
The file itself is not deleted from the database, only the reference in the message.
|
|
"""
|
|
context = await get_context(current_user)
|
|
|
|
# Add detailed logging
|
|
logger.debug(f"DELETE request: Remove file {file_id} from message {message_id} in workflow {workflow_id}")
|
|
|
|
try:
|
|
# Remove file from the message
|
|
success = context.interface_data.delete_file_from_message(workflow_id, message_id, file_id)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"File with ID {file_id} in message {message_id} not found"
|
|
)
|
|
|
|
return {
|
|
"workflow_id": workflow_id,
|
|
"message_id": message_id,
|
|
"file_id": file_id,
|
|
"success": True,
|
|
"message": "File successfully deleted from message"
|
|
}
|
|
|
|
except HTTPException:
|
|
# Forward HTTP exceptions
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error deleting file: {str(e)}")
|
|
import traceback
|
|
traceback_str = traceback.format_exc()
|
|
logger.error(f"Traceback: {traceback_str}")
|
|
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Error deleting file from message: {str(e)}"
|
|
) |