One word everywhere: ReplyPier in UI copy, package and image names, the replypier.site domains (app/api/docs/help), REPLYPIER_* settings and the --rp-* design tokens. - public widget API is window.ReplyPier with replypierId / replypierBaseUrl / replypierConfig / replypierToken, loader at webclient/replypier.min.js; both bundles rebuilt from source - NOTICE, llms.txt, CONTRIBUTING, HELP_CENTER_INFRA and the demo runtime config rewritten; the demo config ships no third-party keys - migration adds the ReplyPier managed model value to the AI model enum - local compose project, database, role, volume and Docker network carry the same name
185 lines
7.8 KiB
Python
185 lines
7.8 KiB
Python
"""
|
|
Copyright 2024-2026 ReplyPier
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
"""
|
|
|
|
from typing import Optional
|
|
from uuid import UUID
|
|
from sqlalchemy.orm import Session
|
|
from app.repositories.workflow import WorkflowRepository
|
|
from app.repositories.agent import AgentRepository
|
|
from app.models.workflow import Workflow
|
|
from app.models.schemas.workflow import WorkflowCreate
|
|
from app.core.logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class WorkflowService:
|
|
def __init__(self, db: Session):
|
|
self.db = db
|
|
self.workflow_repo = WorkflowRepository(db)
|
|
self.agent_repo = AgentRepository(db)
|
|
|
|
def create_workflow(self, workflow_data: WorkflowCreate, created_by: UUID, organization_id: UUID) -> Workflow:
|
|
"""Create a new workflow with validation"""
|
|
|
|
# Validate agent exists and belongs to organization
|
|
agent = self.agent_repo.get_by_id(workflow_data.agent_id)
|
|
if not agent:
|
|
raise ValueError("Agent not found")
|
|
|
|
if agent.organization_id != organization_id:
|
|
raise ValueError("Agent does not belong to your organization")
|
|
|
|
# Check if workflow already exists for this agent
|
|
existing_workflow = self.workflow_repo.get_workflows_by_agent(workflow_data.agent_id)
|
|
if existing_workflow:
|
|
raise ValueError("Agent already has a workflow. Only one workflow per agent is allowed.")
|
|
|
|
# Check if workflow name already exists in organization
|
|
existing_workflow_name = self.workflow_repo.get_by_name(
|
|
workflow_data.name,
|
|
organization_id
|
|
)
|
|
if existing_workflow_name:
|
|
raise ValueError(f"Workflow with name '{workflow_data.name}' already exists")
|
|
|
|
# Create workflow data
|
|
workflow_dict = workflow_data.model_dump()
|
|
workflow_dict.update({
|
|
"created_by": created_by,
|
|
"organization_id": organization_id
|
|
})
|
|
|
|
# Create workflow
|
|
workflow = self.workflow_repo.create_workflow(**workflow_dict)
|
|
|
|
logger.info(f"Created workflow {workflow.id} for agent {workflow_data.agent_id}")
|
|
return workflow
|
|
|
|
def get_workflow_by_agent_id(self, agent_id: UUID, organization_id: UUID) -> Optional[Workflow]:
|
|
"""Get workflow by agent ID with organization validation"""
|
|
|
|
# Validate agent exists and belongs to organization
|
|
agent = self.agent_repo.get_by_id(agent_id)
|
|
if not agent:
|
|
raise ValueError("Agent not found")
|
|
|
|
if agent.organization_id != organization_id:
|
|
raise ValueError("Agent does not belong to your organization")
|
|
|
|
# Get workflows for this agent
|
|
workflows = self.workflow_repo.get_workflows_by_agent(agent_id)
|
|
|
|
# Return the first workflow (should be only one due to our constraint)
|
|
return workflows[0] if workflows else None
|
|
|
|
def update_workflow(self, workflow_id: UUID, workflow_data: dict, organization_id: UUID) -> Optional[Workflow]:
|
|
"""Update workflow with validation"""
|
|
|
|
# Get existing workflow
|
|
workflow = self.workflow_repo.get_by_id(workflow_id)
|
|
if not workflow:
|
|
raise ValueError("Workflow not found")
|
|
|
|
# Verify organization access
|
|
if workflow.organization_id != organization_id:
|
|
raise ValueError("Workflow does not belong to your organization")
|
|
|
|
# If name is being updated, check for duplicates
|
|
if 'name' in workflow_data and workflow_data['name'] != workflow.name:
|
|
existing_workflow = self.workflow_repo.get_by_name(
|
|
workflow_data['name'],
|
|
organization_id
|
|
)
|
|
if existing_workflow and existing_workflow.id != workflow_id:
|
|
raise ValueError(f"Workflow with name '{workflow_data['name']}' already exists")
|
|
|
|
# If agent_id is being updated, validate the new agent and check constraint
|
|
if 'agent_id' in workflow_data and workflow_data['agent_id'] != workflow.agent_id:
|
|
# Validate new agent exists and belongs to organization
|
|
agent = self.agent_repo.get_by_id(workflow_data['agent_id'])
|
|
if not agent:
|
|
raise ValueError("Agent not found")
|
|
|
|
if agent.organization_id != organization_id:
|
|
raise ValueError("Agent does not belong to your organization")
|
|
|
|
# Check if new agent already has a workflow
|
|
existing_workflows = self.workflow_repo.get_workflows_by_agent(workflow_data['agent_id'])
|
|
if existing_workflows:
|
|
raise ValueError("Agent already has a workflow. Only one workflow per agent is allowed.")
|
|
|
|
# Check if status is being updated to published
|
|
if 'status' in workflow_data and workflow_data['status'] == 'published':
|
|
# When publishing, update the agent's active_workflow_id and use_workflow
|
|
agent = self.agent_repo.get_by_id(workflow.agent_id)
|
|
if agent:
|
|
self.agent_repo.update_agent(
|
|
workflow.agent_id,
|
|
active_workflow_id=workflow_id,
|
|
use_workflow=True
|
|
)
|
|
logger.info(f"Updated agent {workflow.agent_id} active_workflow_id to {workflow_id}")
|
|
|
|
# Check if status is being updated to draft (unpublished)
|
|
if 'status' in workflow_data and workflow_data['status'] == 'draft':
|
|
# When unpublishing, clear the agent's active_workflow_id and disable workflow
|
|
agent = self.agent_repo.get_by_id(workflow.agent_id)
|
|
if agent and agent.active_workflow_id == workflow_id:
|
|
self.agent_repo.update_agent(
|
|
workflow.agent_id,
|
|
active_workflow_id=None,
|
|
use_workflow=False
|
|
)
|
|
logger.info(f"Cleared agent {workflow.agent_id} active_workflow_id")
|
|
|
|
# Update workflow
|
|
updated_workflow = self.workflow_repo.update_workflow(workflow_id, **workflow_data)
|
|
|
|
logger.info(f"Updated workflow {workflow_id}")
|
|
return updated_workflow
|
|
|
|
def delete_workflow(self, workflow_id: UUID, organization_id: UUID) -> bool:
|
|
"""Delete workflow with validation"""
|
|
|
|
# Get existing workflow
|
|
workflow = self.workflow_repo.get_by_id(workflow_id)
|
|
if not workflow:
|
|
raise ValueError("Workflow not found")
|
|
|
|
# Verify organization access
|
|
if workflow.organization_id != organization_id:
|
|
raise ValueError("Workflow does not belong to your organization")
|
|
|
|
# Clear agent's active_workflow_id if this workflow is active
|
|
agent = self.agent_repo.get_by_id(workflow.agent_id)
|
|
if agent and agent.active_workflow_id == workflow_id:
|
|
self.agent_repo.update_agent(
|
|
workflow.agent_id,
|
|
active_workflow_id=None,
|
|
use_workflow=False
|
|
)
|
|
logger.info(f"Cleared agent {workflow.agent_id} active_workflow_id before deleting workflow")
|
|
|
|
# Delete workflow
|
|
success = self.workflow_repo.delete_workflow(workflow_id)
|
|
|
|
if success:
|
|
logger.info(f"Deleted workflow {workflow_id}")
|
|
|
|
return success
|
|
|
|
|