mirror of https://github.com/kortix-ai/suna.git
chore(dev): ux refactor for marketplace
This commit is contained in:
parent
120b3974b3
commit
d603125311
|
@ -56,6 +56,26 @@ class AgentCreateRequest(BaseModel):
|
|||
avatar: Optional[str] = None
|
||||
avatar_color: Optional[str] = None
|
||||
|
||||
class AgentVersionResponse(BaseModel):
|
||||
version_id: str
|
||||
agent_id: str
|
||||
version_number: int
|
||||
version_name: str
|
||||
system_prompt: str
|
||||
configured_mcps: List[Dict[str, Any]]
|
||||
custom_mcps: List[Dict[str, Any]]
|
||||
agentpress_tools: Dict[str, Any]
|
||||
is_active: bool
|
||||
created_at: str
|
||||
updated_at: str
|
||||
created_by: Optional[str] = None
|
||||
|
||||
class AgentVersionCreateRequest(BaseModel):
|
||||
system_prompt: str
|
||||
configured_mcps: Optional[List[Dict[str, Any]]] = []
|
||||
custom_mcps: Optional[List[Dict[str, Any]]] = []
|
||||
agentpress_tools: Optional[Dict[str, Any]] = {}
|
||||
|
||||
class AgentUpdateRequest(BaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
|
@ -71,20 +91,23 @@ class AgentResponse(BaseModel):
|
|||
agent_id: str
|
||||
account_id: str
|
||||
name: str
|
||||
description: Optional[str]
|
||||
description: Optional[str] = None
|
||||
system_prompt: str
|
||||
configured_mcps: List[Dict[str, Any]]
|
||||
custom_mcps: Optional[List[Dict[str, Any]]] = []
|
||||
custom_mcps: List[Dict[str, Any]]
|
||||
agentpress_tools: Dict[str, Any]
|
||||
is_default: bool
|
||||
avatar: Optional[str] = None
|
||||
avatar_color: Optional[str] = None
|
||||
created_at: str
|
||||
updated_at: Optional[str] = None
|
||||
is_public: Optional[bool] = False
|
||||
marketplace_published_at: Optional[str] = None
|
||||
download_count: Optional[int] = 0
|
||||
tags: Optional[List[str]] = []
|
||||
avatar: Optional[str]
|
||||
avatar_color: Optional[str]
|
||||
created_at: str
|
||||
updated_at: str
|
||||
current_version_id: Optional[str] = None
|
||||
version_count: Optional[int] = 1
|
||||
current_version: Optional[AgentVersionResponse] = None
|
||||
|
||||
class PaginationInfo(BaseModel):
|
||||
page: int
|
||||
|
@ -387,12 +410,13 @@ async def start_agent(
|
|||
if is_agent_builder:
|
||||
logger.info(f"Thread {thread_id} is in agent builder mode, target_agent_id: {target_agent_id}")
|
||||
|
||||
# Load agent configuration
|
||||
# Load agent configuration with version support
|
||||
agent_config = None
|
||||
effective_agent_id = body.agent_id or thread_agent_id # Use provided agent_id or the one stored in thread
|
||||
|
||||
if effective_agent_id:
|
||||
agent_result = await client.table('agents').select('*').eq('agent_id', effective_agent_id).eq('account_id', account_id).execute()
|
||||
# Get agent with current version
|
||||
agent_result = await client.table('agents').select('*, agent_versions!current_version_id(*)').eq('agent_id', effective_agent_id).eq('account_id', account_id).execute()
|
||||
if not agent_result.data:
|
||||
if body.agent_id:
|
||||
raise HTTPException(status_code=404, detail="Agent not found or access denied")
|
||||
|
@ -400,16 +424,53 @@ async def start_agent(
|
|||
logger.warning(f"Stored agent_id {effective_agent_id} not found, falling back to default")
|
||||
effective_agent_id = None
|
||||
else:
|
||||
agent_config = agent_result.data[0]
|
||||
agent_data = agent_result.data[0]
|
||||
# Use version data if available, otherwise fall back to agent data (for backward compatibility)
|
||||
if agent_data.get('agent_versions'):
|
||||
version_data = agent_data['agent_versions']
|
||||
agent_config = {
|
||||
'agent_id': agent_data['agent_id'],
|
||||
'name': agent_data['name'],
|
||||
'description': agent_data.get('description'),
|
||||
'system_prompt': version_data['system_prompt'],
|
||||
'configured_mcps': version_data.get('configured_mcps', []),
|
||||
'custom_mcps': version_data.get('custom_mcps', []),
|
||||
'agentpress_tools': version_data.get('agentpress_tools', {}),
|
||||
'is_default': agent_data.get('is_default', False),
|
||||
'current_version_id': agent_data.get('current_version_id'),
|
||||
'version_name': version_data.get('version_name', 'v1')
|
||||
}
|
||||
logger.info(f"Using agent {agent_config['name']} ({effective_agent_id}) version {agent_config['version_name']}")
|
||||
else:
|
||||
# Backward compatibility - use agent data directly
|
||||
agent_config = agent_data
|
||||
logger.info(f"Using agent {agent_config['name']} ({effective_agent_id}) - no version data")
|
||||
source = "request" if body.agent_id else "thread"
|
||||
logger.info(f"Using agent from {source}: {agent_config['name']} ({effective_agent_id})")
|
||||
|
||||
# If no agent found yet, try to get default agent for the account
|
||||
if not agent_config:
|
||||
default_agent_result = await client.table('agents').select('*').eq('account_id', account_id).eq('is_default', True).execute()
|
||||
default_agent_result = await client.table('agents').select('*, agent_versions!current_version_id(*)').eq('account_id', account_id).eq('is_default', True).execute()
|
||||
if default_agent_result.data:
|
||||
agent_config = default_agent_result.data[0]
|
||||
logger.info(f"Using default agent: {agent_config['name']} ({agent_config['agent_id']})")
|
||||
agent_data = default_agent_result.data[0]
|
||||
# Use version data if available
|
||||
if agent_data.get('agent_versions'):
|
||||
version_data = agent_data['agent_versions']
|
||||
agent_config = {
|
||||
'agent_id': agent_data['agent_id'],
|
||||
'name': agent_data['name'],
|
||||
'description': agent_data.get('description'),
|
||||
'system_prompt': version_data['system_prompt'],
|
||||
'configured_mcps': version_data.get('configured_mcps', []),
|
||||
'custom_mcps': version_data.get('custom_mcps', []),
|
||||
'agentpress_tools': version_data.get('agentpress_tools', {}),
|
||||
'is_default': agent_data.get('is_default', False),
|
||||
'current_version_id': agent_data.get('current_version_id'),
|
||||
'version_name': version_data.get('version_name', 'v1')
|
||||
}
|
||||
logger.info(f"Using default agent: {agent_config['name']} ({agent_config['agent_id']}) version {agent_config['version_name']}")
|
||||
else:
|
||||
agent_config = agent_data
|
||||
logger.info(f"Using default agent: {agent_config['name']} ({agent_config['agent_id']}) - no version data")
|
||||
|
||||
# Update thread's agent_id if a different agent was explicitly requested
|
||||
if body.agent_id and body.agent_id != thread_agent_id and agent_config:
|
||||
|
@ -1092,8 +1153,8 @@ async def get_agents(
|
|||
# Calculate offset
|
||||
offset = (page - 1) * limit
|
||||
|
||||
# Start building the query
|
||||
query = client.table('agents').select('*', count='exact').eq("account_id", user_id)
|
||||
# Start building the query - include version data
|
||||
query = client.table('agents').select('*, agent_versions!current_version_id(*)', count='exact').eq("account_id", user_id)
|
||||
|
||||
# Apply search filter
|
||||
if search:
|
||||
|
@ -1206,6 +1267,24 @@ async def get_agents(
|
|||
# Format the response
|
||||
agent_list = []
|
||||
for agent in agents_data:
|
||||
current_version = None
|
||||
if agent.get('agent_versions'):
|
||||
version_data = agent['agent_versions']
|
||||
current_version = AgentVersionResponse(
|
||||
version_id=version_data['version_id'],
|
||||
agent_id=version_data['agent_id'],
|
||||
version_number=version_data['version_number'],
|
||||
version_name=version_data['version_name'],
|
||||
system_prompt=version_data['system_prompt'],
|
||||
configured_mcps=version_data.get('configured_mcps', []),
|
||||
custom_mcps=version_data.get('custom_mcps', []),
|
||||
agentpress_tools=version_data.get('agentpress_tools', {}),
|
||||
is_active=version_data.get('is_active', True),
|
||||
created_at=version_data['created_at'],
|
||||
updated_at=version_data.get('updated_at', version_data['created_at']),
|
||||
created_by=version_data.get('created_by')
|
||||
)
|
||||
|
||||
agent_list.append(AgentResponse(
|
||||
agent_id=agent['agent_id'],
|
||||
account_id=agent['account_id'],
|
||||
|
@ -1223,7 +1302,10 @@ async def get_agents(
|
|||
avatar=agent.get('avatar'),
|
||||
avatar_color=agent.get('avatar_color'),
|
||||
created_at=agent['created_at'],
|
||||
updated_at=agent['updated_at']
|
||||
updated_at=agent['updated_at'],
|
||||
current_version_id=agent.get('current_version_id'),
|
||||
version_count=agent.get('version_count', 1),
|
||||
current_version=current_version
|
||||
))
|
||||
|
||||
total_pages = (total_count + limit - 1) // limit
|
||||
|
@ -1245,7 +1327,7 @@ async def get_agents(
|
|||
|
||||
@router.get("/agents/{agent_id}", response_model=AgentResponse)
|
||||
async def get_agent(agent_id: str, user_id: str = Depends(get_current_user_id_from_jwt)):
|
||||
"""Get a specific agent by ID. Only the owner can access non-public agents."""
|
||||
"""Get a specific agent by ID with current version information. Only the owner can access non-public agents."""
|
||||
if not await is_enabled("custom_agents"):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
|
@ -1256,8 +1338,8 @@ async def get_agent(agent_id: str, user_id: str = Depends(get_current_user_id_fr
|
|||
client = await db.client
|
||||
|
||||
try:
|
||||
# Get agent with access check - only owner or public agents
|
||||
agent = await client.table('agents').select('*').eq("agent_id", agent_id).execute()
|
||||
# Get agent with current version data
|
||||
agent = await client.table('agents').select('*, agent_versions!current_version_id(*)').eq("agent_id", agent_id).execute()
|
||||
|
||||
if not agent.data:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
|
@ -1268,6 +1350,25 @@ async def get_agent(agent_id: str, user_id: str = Depends(get_current_user_id_fr
|
|||
if agent_data['account_id'] != user_id and not agent_data.get('is_public', False):
|
||||
raise HTTPException(status_code=403, detail="Access denied")
|
||||
|
||||
# Prepare current version data
|
||||
current_version = None
|
||||
if agent_data.get('agent_versions'):
|
||||
version_data = agent_data['agent_versions']
|
||||
current_version = AgentVersionResponse(
|
||||
version_id=version_data['version_id'],
|
||||
agent_id=version_data['agent_id'],
|
||||
version_number=version_data['version_number'],
|
||||
version_name=version_data['version_name'],
|
||||
system_prompt=version_data['system_prompt'],
|
||||
configured_mcps=version_data.get('configured_mcps', []),
|
||||
custom_mcps=version_data.get('custom_mcps', []),
|
||||
agentpress_tools=version_data.get('agentpress_tools', {}),
|
||||
is_active=version_data.get('is_active', True),
|
||||
created_at=version_data['created_at'],
|
||||
updated_at=version_data.get('updated_at', version_data['created_at']),
|
||||
created_by=version_data.get('created_by')
|
||||
)
|
||||
|
||||
return AgentResponse(
|
||||
agent_id=agent_data['agent_id'],
|
||||
account_id=agent_data['account_id'],
|
||||
|
@ -1285,7 +1386,10 @@ async def get_agent(agent_id: str, user_id: str = Depends(get_current_user_id_fr
|
|||
avatar=agent_data.get('avatar'),
|
||||
avatar_color=agent_data.get('avatar_color'),
|
||||
created_at=agent_data['created_at'],
|
||||
updated_at=agent_data['updated_at']
|
||||
updated_at=agent_data.get('updated_at', agent_data['created_at']),
|
||||
current_version_id=agent_data.get('current_version_id'),
|
||||
version_count=agent_data.get('version_count', 1),
|
||||
current_version=current_version
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
|
@ -1299,7 +1403,7 @@ async def create_agent(
|
|||
agent_data: AgentCreateRequest,
|
||||
user_id: str = Depends(get_current_user_id_from_jwt)
|
||||
):
|
||||
"""Create a new agent."""
|
||||
"""Create a new agent with automatic v1 version."""
|
||||
logger.info(f"Creating new agent for user: {user_id}")
|
||||
if not await is_enabled("custom_agents"):
|
||||
raise HTTPException(
|
||||
|
@ -1313,12 +1417,7 @@ async def create_agent(
|
|||
if agent_data.is_default:
|
||||
await client.table('agents').update({"is_default": False}).eq("account_id", user_id).eq("is_default", True).execute()
|
||||
|
||||
# enhanced_system_prompt = await enhance_system_prompt(
|
||||
# agent_name=agent_data.name,
|
||||
# description=agent_data.description or "",
|
||||
# user_system_prompt=agent_data.system_prompt
|
||||
# )
|
||||
|
||||
# Create the agent
|
||||
insert_data = {
|
||||
"account_id": user_id,
|
||||
"name": agent_data.name,
|
||||
|
@ -1329,7 +1428,8 @@ async def create_agent(
|
|||
"agentpress_tools": agent_data.agentpress_tools or {},
|
||||
"is_default": agent_data.is_default or False,
|
||||
"avatar": agent_data.avatar,
|
||||
"avatar_color": agent_data.avatar_color
|
||||
"avatar_color": agent_data.avatar_color,
|
||||
"version_count": 1
|
||||
}
|
||||
|
||||
new_agent = await client.table('agents').insert(insert_data).execute()
|
||||
|
@ -1338,7 +1438,42 @@ async def create_agent(
|
|||
raise HTTPException(status_code=500, detail="Failed to create agent")
|
||||
|
||||
agent = new_agent.data[0]
|
||||
logger.info(f"Created agent {agent['agent_id']} for user: {user_id}")
|
||||
|
||||
# Create v1 version automatically
|
||||
version_data = {
|
||||
"agent_id": agent['agent_id'],
|
||||
"version_number": 1,
|
||||
"version_name": "v1",
|
||||
"system_prompt": agent_data.system_prompt,
|
||||
"configured_mcps": agent_data.configured_mcps or [],
|
||||
"custom_mcps": agent_data.custom_mcps or [],
|
||||
"agentpress_tools": agent_data.agentpress_tools or {},
|
||||
"is_active": True,
|
||||
"created_by": user_id
|
||||
}
|
||||
|
||||
new_version = await client.table('agent_versions').insert(version_data).execute()
|
||||
|
||||
if new_version.data:
|
||||
version = new_version.data[0]
|
||||
# Update agent with current version
|
||||
await client.table('agents').update({
|
||||
"current_version_id": version['version_id']
|
||||
}).eq("agent_id", agent['agent_id']).execute()
|
||||
|
||||
# Add version history entry
|
||||
await client.table('agent_version_history').insert({
|
||||
"agent_id": agent['agent_id'],
|
||||
"version_id": version['version_id'],
|
||||
"action": "created",
|
||||
"changed_by": user_id,
|
||||
"change_description": "Initial version v1 created"
|
||||
}).execute()
|
||||
|
||||
agent['current_version_id'] = version['version_id']
|
||||
agent['current_version'] = version
|
||||
|
||||
logger.info(f"Created agent {agent['agent_id']} with v1 for user: {user_id}")
|
||||
|
||||
return AgentResponse(
|
||||
agent_id=agent['agent_id'],
|
||||
|
@ -1357,7 +1492,10 @@ async def create_agent(
|
|||
avatar=agent.get('avatar'),
|
||||
avatar_color=agent.get('avatar_color'),
|
||||
created_at=agent['created_at'],
|
||||
updated_at=agent['updated_at']
|
||||
updated_at=agent.get('updated_at', agent['created_at']),
|
||||
current_version_id=agent.get('current_version_id'),
|
||||
version_count=agent.get('version_count', 1),
|
||||
current_version=agent.get('current_version')
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
|
@ -1372,7 +1510,7 @@ async def update_agent(
|
|||
agent_data: AgentUpdateRequest,
|
||||
user_id: str = Depends(get_current_user_id_from_jwt)
|
||||
):
|
||||
"""Update an existing agent."""
|
||||
"""Update an existing agent. Creates a new version if system prompt, tools, or MCPs are changed."""
|
||||
if not await is_enabled("custom_agents"):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
|
@ -1382,34 +1520,41 @@ async def update_agent(
|
|||
client = await db.client
|
||||
|
||||
try:
|
||||
# First verify the agent exists and belongs to the user
|
||||
existing_agent = await client.table('agents').select('*').eq("agent_id", agent_id).eq("account_id", user_id).maybe_single().execute()
|
||||
# First verify the agent exists and belongs to the user, get with current version
|
||||
existing_agent = await client.table('agents').select('*, agent_versions!current_version_id(*)').eq("agent_id", agent_id).eq("account_id", user_id).maybe_single().execute()
|
||||
|
||||
if not existing_agent.data:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
|
||||
existing_data = existing_agent.data
|
||||
current_version_data = existing_data.get('agent_versions', {})
|
||||
|
||||
# Prepare update data (only include fields that are not None)
|
||||
# Check if we need to create a new version (if system prompt, tools, or MCPs are changing)
|
||||
needs_new_version = False
|
||||
version_changes = {}
|
||||
|
||||
if agent_data.system_prompt is not None and agent_data.system_prompt != current_version_data.get('system_prompt'):
|
||||
needs_new_version = True
|
||||
version_changes['system_prompt'] = agent_data.system_prompt
|
||||
|
||||
if agent_data.configured_mcps is not None and agent_data.configured_mcps != current_version_data.get('configured_mcps', []):
|
||||
needs_new_version = True
|
||||
version_changes['configured_mcps'] = agent_data.configured_mcps
|
||||
|
||||
if agent_data.custom_mcps is not None and agent_data.custom_mcps != current_version_data.get('custom_mcps', []):
|
||||
needs_new_version = True
|
||||
version_changes['custom_mcps'] = agent_data.custom_mcps
|
||||
|
||||
if agent_data.agentpress_tools is not None and agent_data.agentpress_tools != current_version_data.get('agentpress_tools', {}):
|
||||
needs_new_version = True
|
||||
version_changes['agentpress_tools'] = agent_data.agentpress_tools
|
||||
|
||||
# Prepare update data for agent metadata (non-versioned fields)
|
||||
update_data = {}
|
||||
if agent_data.name is not None:
|
||||
update_data["name"] = agent_data.name
|
||||
if agent_data.description is not None:
|
||||
update_data["description"] = agent_data.description
|
||||
if agent_data.system_prompt is not None:
|
||||
# Enhance the system prompt using GPT-4o if it's being updated
|
||||
# enhanced_system_prompt = await enhance_system_prompt(
|
||||
# agent_name=agent_data.name or existing_data['name'],
|
||||
# description=agent_data.description or existing_data.get('description', ''),
|
||||
# user_system_prompt=agent_data.system_prompt
|
||||
# )
|
||||
update_data["system_prompt"] = agent_data.system_prompt
|
||||
if agent_data.configured_mcps is not None:
|
||||
update_data["configured_mcps"] = agent_data.configured_mcps
|
||||
if agent_data.custom_mcps is not None:
|
||||
update_data["custom_mcps"] = agent_data.custom_mcps
|
||||
if agent_data.agentpress_tools is not None:
|
||||
update_data["agentpress_tools"] = agent_data.agentpress_tools
|
||||
if agent_data.is_default is not None:
|
||||
update_data["is_default"] = agent_data.is_default
|
||||
# If setting as default, unset other defaults first
|
||||
|
@ -1420,23 +1565,89 @@ async def update_agent(
|
|||
if agent_data.avatar_color is not None:
|
||||
update_data["avatar_color"] = agent_data.avatar_color
|
||||
|
||||
if not update_data:
|
||||
# No fields to update, return existing agent
|
||||
agent = existing_agent.data
|
||||
else:
|
||||
# Update the agent
|
||||
# Also update the agent table with the latest values (for backward compatibility)
|
||||
if agent_data.system_prompt is not None:
|
||||
update_data["system_prompt"] = agent_data.system_prompt
|
||||
if agent_data.configured_mcps is not None:
|
||||
update_data["configured_mcps"] = agent_data.configured_mcps
|
||||
if agent_data.custom_mcps is not None:
|
||||
update_data["custom_mcps"] = agent_data.custom_mcps
|
||||
if agent_data.agentpress_tools is not None:
|
||||
update_data["agentpress_tools"] = agent_data.agentpress_tools
|
||||
|
||||
# Create new version if needed
|
||||
new_version_id = None
|
||||
if needs_new_version:
|
||||
# Get next version number
|
||||
versions_result = await client.table('agent_versions').select('version_number').eq('agent_id', agent_id).order('version_number', desc=True).limit(1).execute()
|
||||
next_version_number = 1
|
||||
if versions_result.data:
|
||||
next_version_number = versions_result.data[0]['version_number'] + 1
|
||||
|
||||
# Create new version with current data merged with changes
|
||||
new_version_data = {
|
||||
"agent_id": agent_id,
|
||||
"version_number": next_version_number,
|
||||
"version_name": f"v{next_version_number}",
|
||||
"system_prompt": version_changes.get('system_prompt', current_version_data.get('system_prompt')),
|
||||
"configured_mcps": version_changes.get('configured_mcps', current_version_data.get('configured_mcps', [])),
|
||||
"custom_mcps": version_changes.get('custom_mcps', current_version_data.get('custom_mcps', [])),
|
||||
"agentpress_tools": version_changes.get('agentpress_tools', current_version_data.get('agentpress_tools', {})),
|
||||
"is_active": True,
|
||||
"created_by": user_id
|
||||
}
|
||||
|
||||
new_version = await client.table('agent_versions').insert(new_version_data).execute()
|
||||
|
||||
if new_version.data:
|
||||
new_version_id = new_version.data[0]['version_id']
|
||||
update_data['current_version_id'] = new_version_id
|
||||
update_data['version_count'] = next_version_number
|
||||
|
||||
# Add version history entry
|
||||
await client.table('agent_version_history').insert({
|
||||
"agent_id": agent_id,
|
||||
"version_id": new_version_id,
|
||||
"action": "created",
|
||||
"changed_by": user_id,
|
||||
"change_description": f"New version v{next_version_number} created from update"
|
||||
}).execute()
|
||||
|
||||
logger.info(f"Created new version v{next_version_number} for agent {agent_id}")
|
||||
|
||||
# Update the agent if there are changes
|
||||
if update_data:
|
||||
update_result = await client.table('agents').update(update_data).eq("agent_id", agent_id).eq("account_id", user_id).execute()
|
||||
|
||||
if not update_result.data:
|
||||
raise HTTPException(status_code=500, detail="Failed to update agent")
|
||||
|
||||
# Fetch the updated agent data
|
||||
updated_agent = await client.table('agents').select('*').eq("agent_id", agent_id).eq("account_id", user_id).maybe_single().execute()
|
||||
|
||||
if not updated_agent.data:
|
||||
raise HTTPException(status_code=500, detail="Failed to fetch updated agent")
|
||||
|
||||
agent = updated_agent.data
|
||||
|
||||
# Fetch the updated agent data with version info
|
||||
updated_agent = await client.table('agents').select('*, agent_versions!current_version_id(*)').eq("agent_id", agent_id).eq("account_id", user_id).maybe_single().execute()
|
||||
|
||||
if not updated_agent.data:
|
||||
raise HTTPException(status_code=500, detail="Failed to fetch updated agent")
|
||||
|
||||
agent = updated_agent.data
|
||||
|
||||
# Prepare current version response
|
||||
current_version = None
|
||||
if agent.get('agent_versions'):
|
||||
version_data = agent['agent_versions']
|
||||
current_version = AgentVersionResponse(
|
||||
version_id=version_data['version_id'],
|
||||
agent_id=version_data['agent_id'],
|
||||
version_number=version_data['version_number'],
|
||||
version_name=version_data['version_name'],
|
||||
system_prompt=version_data['system_prompt'],
|
||||
configured_mcps=version_data.get('configured_mcps', []),
|
||||
custom_mcps=version_data.get('custom_mcps', []),
|
||||
agentpress_tools=version_data.get('agentpress_tools', {}),
|
||||
is_active=version_data.get('is_active', True),
|
||||
created_at=version_data['created_at'],
|
||||
updated_at=version_data.get('updated_at', version_data['created_at']),
|
||||
created_by=version_data.get('created_by')
|
||||
)
|
||||
|
||||
logger.info(f"Updated agent {agent_id} for user: {user_id}")
|
||||
|
||||
|
@ -1457,7 +1668,10 @@ async def update_agent(
|
|||
avatar=agent.get('avatar'),
|
||||
avatar_color=agent.get('avatar_color'),
|
||||
created_at=agent['created_at'],
|
||||
updated_at=agent['updated_at']
|
||||
updated_at=agent.get('updated_at', agent['created_at']),
|
||||
current_version_id=agent.get('current_version_id'),
|
||||
version_count=agent.get('version_count', 1),
|
||||
current_version=current_version
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
|
@ -1820,3 +2034,147 @@ async def get_agent_builder_chat_history(
|
|||
except Exception as e:
|
||||
logger.error(f"Error fetching agent builder chat history for agent {agent_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Failed to fetch chat history: {str(e)}")
|
||||
|
||||
@router.get("/agents/{agent_id}/versions", response_model=List[AgentVersionResponse])
|
||||
async def get_agent_versions(
|
||||
agent_id: str,
|
||||
user_id: str = Depends(get_current_user_id_from_jwt)
|
||||
):
|
||||
"""Get all versions of an agent."""
|
||||
client = await db.client
|
||||
|
||||
# Check if user has access to this agent
|
||||
agent_result = await client.table('agents').select("*").eq("agent_id", agent_id).execute()
|
||||
if not agent_result.data:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
|
||||
agent = agent_result.data[0]
|
||||
if agent['account_id'] != user_id and not agent.get('is_public', False):
|
||||
raise HTTPException(status_code=403, detail="Access denied")
|
||||
|
||||
# Get all versions
|
||||
versions_result = await client.table('agent_versions').select("*").eq("agent_id", agent_id).order("version_number", desc=True).execute()
|
||||
|
||||
return versions_result.data
|
||||
|
||||
@router.post("/agents/{agent_id}/versions", response_model=AgentVersionResponse)
|
||||
async def create_agent_version(
|
||||
agent_id: str,
|
||||
version_data: AgentVersionCreateRequest,
|
||||
user_id: str = Depends(get_current_user_id_from_jwt)
|
||||
):
|
||||
"""Create a new version of an agent."""
|
||||
client = await db.client
|
||||
|
||||
# Check if user owns this agent
|
||||
agent_result = await client.table('agents').select("*").eq("agent_id", agent_id).eq("account_id", user_id).execute()
|
||||
if not agent_result.data:
|
||||
raise HTTPException(status_code=404, detail="Agent not found or access denied")
|
||||
|
||||
agent = agent_result.data[0]
|
||||
|
||||
# Get next version number
|
||||
versions_result = await client.table('agent_versions').select("version_number").eq("agent_id", agent_id).order("version_number", desc=True).limit(1).execute()
|
||||
next_version_number = 1
|
||||
if versions_result.data:
|
||||
next_version_number = versions_result.data[0]['version_number'] + 1
|
||||
|
||||
# Create new version
|
||||
new_version_data = {
|
||||
"agent_id": agent_id,
|
||||
"version_number": next_version_number,
|
||||
"version_name": f"v{next_version_number}",
|
||||
"system_prompt": version_data.system_prompt,
|
||||
"configured_mcps": version_data.configured_mcps or [],
|
||||
"custom_mcps": version_data.custom_mcps or [],
|
||||
"agentpress_tools": version_data.agentpress_tools or {},
|
||||
"is_active": True,
|
||||
"created_by": user_id
|
||||
}
|
||||
|
||||
new_version = await client.table('agent_versions').insert(new_version_data).execute()
|
||||
|
||||
if not new_version.data:
|
||||
raise HTTPException(status_code=500, detail="Failed to create version")
|
||||
|
||||
version = new_version.data[0]
|
||||
|
||||
# Update agent with new version
|
||||
await client.table('agents').update({
|
||||
"current_version_id": version['version_id'],
|
||||
"version_count": next_version_number
|
||||
}).eq("agent_id", agent_id).execute()
|
||||
|
||||
# Add version history entry
|
||||
await client.table('agent_version_history').insert({
|
||||
"agent_id": agent_id,
|
||||
"version_id": version['version_id'],
|
||||
"action": "created",
|
||||
"changed_by": user_id,
|
||||
"change_description": f"New version v{next_version_number} created"
|
||||
}).execute()
|
||||
|
||||
logger.info(f"Created version v{next_version_number} for agent {agent_id}")
|
||||
|
||||
return version
|
||||
|
||||
@router.put("/agents/{agent_id}/versions/{version_id}/activate")
|
||||
async def activate_agent_version(
|
||||
agent_id: str,
|
||||
version_id: str,
|
||||
user_id: str = Depends(get_current_user_id_from_jwt)
|
||||
):
|
||||
"""Switch agent to use a specific version."""
|
||||
client = await db.client
|
||||
|
||||
# Check if user owns this agent
|
||||
agent_result = await client.table('agents').select("*").eq("agent_id", agent_id).eq("account_id", user_id).execute()
|
||||
if not agent_result.data:
|
||||
raise HTTPException(status_code=404, detail="Agent not found or access denied")
|
||||
|
||||
# Check if version exists
|
||||
version_result = await client.table('agent_versions').select("*").eq("version_id", version_id).eq("agent_id", agent_id).execute()
|
||||
if not version_result.data:
|
||||
raise HTTPException(status_code=404, detail="Version not found")
|
||||
|
||||
# Update agent's current version
|
||||
await client.table('agents').update({
|
||||
"current_version_id": version_id
|
||||
}).eq("agent_id", agent_id).execute()
|
||||
|
||||
# Add version history entry
|
||||
await client.table('agent_version_history').insert({
|
||||
"agent_id": agent_id,
|
||||
"version_id": version_id,
|
||||
"action": "activated",
|
||||
"changed_by": user_id,
|
||||
"change_description": f"Switched to version {version_result.data[0]['version_name']}"
|
||||
}).execute()
|
||||
|
||||
return {"message": "Version activated successfully"}
|
||||
|
||||
@router.get("/agents/{agent_id}/versions/{version_id}", response_model=AgentVersionResponse)
|
||||
async def get_agent_version(
|
||||
agent_id: str,
|
||||
version_id: str,
|
||||
user_id: str = Depends(get_current_user_id_from_jwt)
|
||||
):
|
||||
"""Get a specific version of an agent."""
|
||||
client = await db.client
|
||||
|
||||
# Check if user has access to this agent
|
||||
agent_result = await client.table('agents').select("*").eq("agent_id", agent_id).execute()
|
||||
if not agent_result.data:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
|
||||
agent = agent_result.data[0]
|
||||
if agent['account_id'] != user_id and not agent.get('is_public', False):
|
||||
raise HTTPException(status_code=403, detail="Access denied")
|
||||
|
||||
# Get the specific version
|
||||
version_result = await client.table('agent_versions').select("*").eq("version_id", version_id).eq("agent_id", agent_id).execute()
|
||||
|
||||
if not version_result.data:
|
||||
raise HTTPException(status_code=404, detail="Version not found")
|
||||
|
||||
return version_result.data[0]
|
||||
|
|
|
@ -39,6 +39,7 @@ class AgentTemplate:
|
|||
updated_at: datetime
|
||||
avatar: Optional[str]
|
||||
avatar_color: Optional[str]
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -86,8 +87,8 @@ class TemplateManager:
|
|||
try:
|
||||
client = await db.client
|
||||
|
||||
# Get the existing agent
|
||||
agent_result = await client.table('agents').select('*').eq('agent_id', agent_id).execute()
|
||||
# Get the existing agent with current version
|
||||
agent_result = await client.table('agents').select('*, agent_versions!current_version_id(*)').eq('agent_id', agent_id).execute()
|
||||
if not agent_result.data:
|
||||
raise ValueError("Agent not found")
|
||||
|
||||
|
@ -122,18 +123,34 @@ class TemplateManager:
|
|||
logger.info(f"Created custom MCP requirement: {requirement}")
|
||||
mcp_requirements.append(requirement)
|
||||
|
||||
# Use version data if available, otherwise fall back to agent data
|
||||
version_data = agent.get('agent_versions', {})
|
||||
if version_data:
|
||||
system_prompt = version_data.get('system_prompt', agent['system_prompt'])
|
||||
agentpress_tools = version_data.get('agentpress_tools', agent.get('agentpress_tools', {}))
|
||||
version_name = version_data.get('version_name', 'v1')
|
||||
else:
|
||||
system_prompt = agent['system_prompt']
|
||||
agentpress_tools = agent.get('agentpress_tools', {})
|
||||
version_name = 'v1'
|
||||
|
||||
# Create template
|
||||
template_data = {
|
||||
'creator_id': creator_id,
|
||||
'name': agent['name'],
|
||||
'description': agent.get('description'),
|
||||
'system_prompt': agent['system_prompt'],
|
||||
'system_prompt': system_prompt,
|
||||
'mcp_requirements': mcp_requirements,
|
||||
'agentpress_tools': agent.get('agentpress_tools', {}),
|
||||
'agentpress_tools': agentpress_tools,
|
||||
'tags': tags or [],
|
||||
'is_public': make_public,
|
||||
'avatar': agent.get('avatar'),
|
||||
'avatar_color': agent.get('avatar_color')
|
||||
'avatar_color': agent.get('avatar_color'),
|
||||
'metadata': {
|
||||
'source_agent_id': agent_id,
|
||||
'source_version_id': agent.get('current_version_id'),
|
||||
'source_version_name': version_name
|
||||
}
|
||||
}
|
||||
|
||||
if make_public:
|
||||
|
@ -192,7 +209,8 @@ class TemplateManager:
|
|||
created_at=template_data['created_at'],
|
||||
updated_at=template_data['updated_at'],
|
||||
avatar=template_data.get('avatar'),
|
||||
avatar_color=template_data.get('avatar_color')
|
||||
avatar_color=template_data.get('avatar_color'),
|
||||
metadata=template_data.get('metadata', {})
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
@ -0,0 +1,332 @@
|
|||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agent_versions (
|
||||
version_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
agent_id UUID NOT NULL REFERENCES agents(agent_id) ON DELETE CASCADE,
|
||||
version_number INTEGER NOT NULL,
|
||||
version_name VARCHAR(50) NOT NULL,
|
||||
system_prompt TEXT NOT NULL,
|
||||
configured_mcps JSONB DEFAULT '[]'::jsonb,
|
||||
custom_mcps JSONB DEFAULT '[]'::jsonb,
|
||||
agentpress_tools JSONB DEFAULT '{}'::jsonb,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
created_by UUID REFERENCES basejump.accounts(id),
|
||||
|
||||
UNIQUE(agent_id, version_number),
|
||||
UNIQUE(agent_id, version_name)
|
||||
);
|
||||
|
||||
-- Indexes for agent_versions
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_versions_agent_id ON agent_versions(agent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_versions_version_number ON agent_versions(version_number);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_versions_is_active ON agent_versions(is_active);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_versions_created_at ON agent_versions(created_at);
|
||||
|
||||
-- Add current version tracking to agents table
|
||||
ALTER TABLE agents ADD COLUMN IF NOT EXISTS current_version_id UUID REFERENCES agent_versions(version_id);
|
||||
ALTER TABLE agents ADD COLUMN IF NOT EXISTS version_count INTEGER DEFAULT 1;
|
||||
|
||||
-- Add index for current version
|
||||
CREATE INDEX IF NOT EXISTS idx_agents_current_version ON agents(current_version_id);
|
||||
|
||||
-- Add version tracking to threads (which version is being used in this thread)
|
||||
ALTER TABLE threads ADD COLUMN IF NOT EXISTS agent_version_id UUID REFERENCES agent_versions(version_id);
|
||||
|
||||
-- Add index for thread version
|
||||
CREATE INDEX IF NOT EXISTS idx_threads_agent_version ON threads(agent_version_id);
|
||||
|
||||
-- Track version changes and history
|
||||
CREATE TABLE IF NOT EXISTS agent_version_history (
|
||||
history_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
agent_id UUID NOT NULL REFERENCES agents(agent_id) ON DELETE CASCADE,
|
||||
version_id UUID NOT NULL REFERENCES agent_versions(version_id) ON DELETE CASCADE,
|
||||
action VARCHAR(50) NOT NULL, -- 'created', 'updated', 'activated', 'deactivated'
|
||||
changed_by UUID REFERENCES basejump.accounts(id),
|
||||
change_description TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for version history
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_version_history_agent_id ON agent_version_history(agent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_version_history_version_id ON agent_version_history(version_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_version_history_created_at ON agent_version_history(created_at);
|
||||
|
||||
-- Update updated_at timestamp for agent_versions
|
||||
CREATE OR REPLACE FUNCTION update_agent_versions_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trigger_agent_versions_updated_at
|
||||
BEFORE UPDATE ON agent_versions
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_agent_versions_updated_at();
|
||||
|
||||
-- Enable RLS on new tables
|
||||
ALTER TABLE agent_versions ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE agent_version_history ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Policies for agent_versions
|
||||
CREATE POLICY agent_versions_select_policy ON agent_versions
|
||||
FOR SELECT
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM agents
|
||||
WHERE agents.agent_id = agent_versions.agent_id
|
||||
AND (
|
||||
agents.is_public = TRUE OR
|
||||
basejump.has_role_on_account(agents.account_id)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY agent_versions_insert_policy ON agent_versions
|
||||
FOR INSERT
|
||||
WITH CHECK (
|
||||
EXISTS (
|
||||
SELECT 1 FROM agents
|
||||
WHERE agents.agent_id = agent_versions.agent_id
|
||||
AND basejump.has_role_on_account(agents.account_id, 'owner')
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY agent_versions_update_policy ON agent_versions
|
||||
FOR UPDATE
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM agents
|
||||
WHERE agents.agent_id = agent_versions.agent_id
|
||||
AND basejump.has_role_on_account(agents.account_id, 'owner')
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY agent_versions_delete_policy ON agent_versions
|
||||
FOR DELETE
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM agents
|
||||
WHERE agents.agent_id = agent_versions.agent_id
|
||||
AND basejump.has_role_on_account(agents.account_id, 'owner')
|
||||
)
|
||||
);
|
||||
|
||||
-- Policies for agent_version_history
|
||||
CREATE POLICY agent_version_history_select_policy ON agent_version_history
|
||||
FOR SELECT
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM agents
|
||||
WHERE agents.agent_id = agent_version_history.agent_id
|
||||
AND basejump.has_role_on_account(agents.account_id)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY agent_version_history_insert_policy ON agent_version_history
|
||||
FOR INSERT
|
||||
WITH CHECK (
|
||||
EXISTS (
|
||||
SELECT 1 FROM agents
|
||||
WHERE agents.agent_id = agent_version_history.agent_id
|
||||
AND basejump.has_role_on_account(agents.account_id, 'owner')
|
||||
)
|
||||
);
|
||||
|
||||
-- Function to migrate existing agents to versioned system
|
||||
CREATE OR REPLACE FUNCTION migrate_agents_to_versioned()
|
||||
RETURNS void
|
||||
SECURITY DEFINER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_agent RECORD;
|
||||
v_version_id UUID;
|
||||
BEGIN
|
||||
-- For each existing agent, create a v1 version
|
||||
FOR v_agent IN SELECT * FROM agents WHERE current_version_id IS NULL
|
||||
LOOP
|
||||
-- Create v1 version with current agent data
|
||||
INSERT INTO agent_versions (
|
||||
agent_id,
|
||||
version_number,
|
||||
version_name,
|
||||
system_prompt,
|
||||
configured_mcps,
|
||||
custom_mcps,
|
||||
agentpress_tools,
|
||||
is_active,
|
||||
created_by
|
||||
) VALUES (
|
||||
v_agent.agent_id,
|
||||
1,
|
||||
'v1',
|
||||
v_agent.system_prompt,
|
||||
v_agent.configured_mcps,
|
||||
v_agent.custom_mcps,
|
||||
v_agent.agentpress_tools,
|
||||
TRUE,
|
||||
v_agent.account_id
|
||||
) RETURNING version_id INTO v_version_id;
|
||||
|
||||
-- Update agent with current version
|
||||
UPDATE agents
|
||||
SET current_version_id = v_version_id,
|
||||
version_count = 1
|
||||
WHERE agent_id = v_agent.agent_id;
|
||||
|
||||
-- Add history entry
|
||||
INSERT INTO agent_version_history (
|
||||
agent_id,
|
||||
version_id,
|
||||
action,
|
||||
changed_by,
|
||||
change_description
|
||||
) VALUES (
|
||||
v_agent.agent_id,
|
||||
v_version_id,
|
||||
'created',
|
||||
v_agent.account_id,
|
||||
'Initial version created from existing agent'
|
||||
);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Function to create a new version of an agent
|
||||
CREATE OR REPLACE FUNCTION create_agent_version(
|
||||
p_agent_id UUID,
|
||||
p_system_prompt TEXT,
|
||||
p_configured_mcps JSONB DEFAULT '[]'::jsonb,
|
||||
p_custom_mcps JSONB DEFAULT '[]'::jsonb,
|
||||
p_agentpress_tools JSONB DEFAULT '{}'::jsonb,
|
||||
p_created_by UUID DEFAULT NULL
|
||||
)
|
||||
RETURNS UUID
|
||||
SECURITY DEFINER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_version_id UUID;
|
||||
v_version_number INTEGER;
|
||||
v_version_name VARCHAR(50);
|
||||
BEGIN
|
||||
-- Check if user has permission
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM agents
|
||||
WHERE agent_id = p_agent_id
|
||||
AND basejump.has_role_on_account(account_id, 'owner')
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Agent not found or access denied';
|
||||
END IF;
|
||||
|
||||
-- Get next version number
|
||||
SELECT COALESCE(MAX(version_number), 0) + 1 INTO v_version_number
|
||||
FROM agent_versions
|
||||
WHERE agent_id = p_agent_id;
|
||||
|
||||
-- Generate version name
|
||||
v_version_name := 'v' || v_version_number;
|
||||
|
||||
-- Create new version
|
||||
INSERT INTO agent_versions (
|
||||
agent_id,
|
||||
version_number,
|
||||
version_name,
|
||||
system_prompt,
|
||||
configured_mcps,
|
||||
custom_mcps,
|
||||
agentpress_tools,
|
||||
is_active,
|
||||
created_by
|
||||
) VALUES (
|
||||
p_agent_id,
|
||||
v_version_number,
|
||||
v_version_name,
|
||||
p_system_prompt,
|
||||
p_configured_mcps,
|
||||
p_custom_mcps,
|
||||
p_agentpress_tools,
|
||||
TRUE,
|
||||
p_created_by
|
||||
) RETURNING version_id INTO v_version_id;
|
||||
|
||||
-- Update agent version count
|
||||
UPDATE agents
|
||||
SET version_count = v_version_number,
|
||||
current_version_id = v_version_id
|
||||
WHERE agent_id = p_agent_id;
|
||||
|
||||
-- Add history entry
|
||||
INSERT INTO agent_version_history (
|
||||
agent_id,
|
||||
version_id,
|
||||
action,
|
||||
changed_by,
|
||||
change_description
|
||||
) VALUES (
|
||||
p_agent_id,
|
||||
v_version_id,
|
||||
'created',
|
||||
p_created_by,
|
||||
'New version ' || v_version_name || ' created'
|
||||
);
|
||||
|
||||
RETURN v_version_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Function to switch agent to a different version
|
||||
CREATE OR REPLACE FUNCTION switch_agent_version(
|
||||
p_agent_id UUID,
|
||||
p_version_id UUID,
|
||||
p_changed_by UUID DEFAULT NULL
|
||||
)
|
||||
RETURNS void
|
||||
SECURITY DEFINER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Check if user has permission and version exists
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM agents a
|
||||
JOIN agent_versions av ON a.agent_id = av.agent_id
|
||||
WHERE a.agent_id = p_agent_id
|
||||
AND av.version_id = p_version_id
|
||||
AND basejump.has_role_on_account(a.account_id, 'owner')
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Agent/version not found or access denied';
|
||||
END IF;
|
||||
|
||||
-- Update current version
|
||||
UPDATE agents
|
||||
SET current_version_id = p_version_id
|
||||
WHERE agent_id = p_agent_id;
|
||||
|
||||
-- Add history entry
|
||||
INSERT INTO agent_version_history (
|
||||
agent_id,
|
||||
version_id,
|
||||
action,
|
||||
changed_by,
|
||||
change_description
|
||||
) VALUES (
|
||||
p_agent_id,
|
||||
p_version_id,
|
||||
'activated',
|
||||
p_changed_by,
|
||||
'Switched to this version'
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- =====================================================
|
||||
-- 9. RUN MIGRATION
|
||||
-- =====================================================
|
||||
-- Migrate existing agents to versioned system
|
||||
SELECT migrate_agents_to_versioned();
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,6 @@
|
|||
BEGIN;
|
||||
|
||||
ALTER TABLE agent_templates ADD COLUMN IF NOT EXISTS metadata JSONB DEFAULT '{}'::jsonb;
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_templates_metadata ON agent_templates USING gin(metadata);
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,202 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import {
|
||||
Clock,
|
||||
GitBranch,
|
||||
CheckCircle2,
|
||||
ArrowUpRight,
|
||||
History,
|
||||
Plus
|
||||
} from 'lucide-react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { useAgentVersions, useActivateAgentVersion } from '@/hooks/react-query/agents/useAgentVersions';
|
||||
import { Agent, AgentVersion } from '../_types';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface AgentVersionManagerProps {
|
||||
agent: Agent;
|
||||
onCreateVersion?: () => void;
|
||||
}
|
||||
|
||||
export function AgentVersionManager({ agent, onCreateVersion }: AgentVersionManagerProps) {
|
||||
const { data: versions, isLoading } = useAgentVersions(agent.agent_id);
|
||||
const activateVersion = useActivateAgentVersion();
|
||||
const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const currentVersion = versions?.find(v => v.version_id === agent.current_version_id);
|
||||
const versionHistory = versions?.sort((a, b) => b.version_number - a.version_number) || [];
|
||||
|
||||
const handleActivateVersion = (versionId: string) => {
|
||||
activateVersion.mutate({
|
||||
agentId: agent.agent_id,
|
||||
versionId
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<GitBranch className="h-5 w-5" />
|
||||
Version Management
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Manage different versions of your agent configuration
|
||||
</CardDescription>
|
||||
</div>
|
||||
{onCreateVersion && (
|
||||
<Button onClick={onCreateVersion} size="sm">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
New Version
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="current" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="current">Current Version</TabsTrigger>
|
||||
<TabsTrigger value="history">Version History</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="current" className="space-y-4">
|
||||
{currentVersion ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="default" className="text-sm">
|
||||
{currentVersion.version_name}
|
||||
</Badge>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Active version
|
||||
</span>
|
||||
</div>
|
||||
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Created</span>
|
||||
<span>{formatDistanceToNow(new Date(currentVersion.created_at), { addSuffix: true })}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Tools</span>
|
||||
<span>{Object.keys(currentVersion.agentpress_tools || {}).length} enabled</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">MCP Servers</span>
|
||||
<span>{(currentVersion.configured_mcps?.length || 0) + (currentVersion.custom_mcps?.length || 0)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
No version information available
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="history" className="space-y-4">
|
||||
<ScrollArea className="h-[400px] pr-4">
|
||||
<div className="space-y-3">
|
||||
{versionHistory.map((version, index) => {
|
||||
const isActive = version.version_id === agent.current_version_id;
|
||||
const isSelected = version.version_id === selectedVersion;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={version.version_id}
|
||||
className={cn(
|
||||
"p-4 rounded-lg border cursor-pointer transition-colors",
|
||||
isActive && "border-primary bg-primary/5",
|
||||
!isActive && "hover:bg-muted/50",
|
||||
isSelected && !isActive && "bg-muted"
|
||||
)}
|
||||
onClick={() => setSelectedVersion(version.version_id)}
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant={isActive ? "default" : "secondary"}>
|
||||
{version.version_name}
|
||||
</Badge>
|
||||
{isActive && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
Current
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Created {formatDistanceToNow(new Date(version.created_at), { addSuffix: true })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!isActive && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleActivateVersion(version.version_id);
|
||||
}}
|
||||
disabled={activateVersion.isPending}
|
||||
>
|
||||
<ArrowUpRight className="h-4 w-4 mr-1" />
|
||||
Activate
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isSelected && (
|
||||
<div className="mt-3 pt-3 border-t space-y-2">
|
||||
<div className="grid gap-1 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Tools</span>
|
||||
<span>{Object.keys(version.agentpress_tools || {}).length} enabled</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">MCP Servers</span>
|
||||
<span>{(version.configured_mcps?.length || 0) + (version.custom_mcps?.length || 0)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{versionHistory.length === 0 && (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
<History className="h-12 w-12 mx-auto mb-3 opacity-50" />
|
||||
<p>No version history available</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Settings, Trash2, Star, MessageCircle, Wrench, Globe, GlobeLock, Download, Shield, AlertTriangle } from 'lucide-react';
|
||||
import { Settings, Trash2, Star, MessageCircle, Wrench, Globe, GlobeLock, Download, Shield, AlertTriangle, GitBranch } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
|
||||
import { Dialog, DialogContent, DialogTitle, DialogHeader, DialogDescription } from '@/components/ui/dialog';
|
||||
|
@ -26,6 +26,13 @@ interface Agent {
|
|||
avatar?: string;
|
||||
avatar_color?: string;
|
||||
template_id?: string;
|
||||
current_version_id?: string;
|
||||
version_count?: number;
|
||||
current_version?: {
|
||||
version_id: string;
|
||||
version_name: string;
|
||||
version_number: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface AgentsGridProps {
|
||||
|
@ -76,6 +83,12 @@ const AgentModal = ({ agent, isOpen, onClose, onCustomize, onChat, onPublish, on
|
|||
<h2 className="text-xl font-semibold text-foreground">
|
||||
{agent.name}
|
||||
</h2>
|
||||
{agent.current_version && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<GitBranch className="h-3 w-3" />
|
||||
{agent.current_version.version_name}
|
||||
</Badge>
|
||||
)}
|
||||
{agent.is_public && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Shield className="h-3 w-3" />
|
||||
|
@ -281,6 +294,12 @@ export const AgentsGrid = ({
|
|||
<h3 className="text-foreground font-medium text-lg line-clamp-1 flex-1">
|
||||
{agent.name}
|
||||
</h3>
|
||||
{agent.current_version && (
|
||||
<Badge variant="outline" className="text-xs shrink-0">
|
||||
<GitBranch className="h-3 w-3" />
|
||||
{agent.current_version.version_name}
|
||||
</Badge>
|
||||
)}
|
||||
{agent.is_public && (
|
||||
<Badge variant="outline" className="text-xs shrink-0">
|
||||
<Shield className="h-3 w-3" />
|
||||
|
|
|
@ -6,13 +6,15 @@ import { Textarea } from '@/components/ui/textarea';
|
|||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Loader2, Search, Save, Settings2, Sparkles } from 'lucide-react';
|
||||
import { Loader2, Search, Save, Settings2, Sparkles, GitBranch } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { DEFAULT_AGENTPRESS_TOOLS, getToolDisplayName } from '../_data/tools';
|
||||
import { useAgent, useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
import { MCPConfiguration } from './mcp-configuration';
|
||||
import { MCPConfigurationNew } from './mcp/mcp-configuration-new';
|
||||
import { AgentVersionManager } from './AgentVersionManager';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
interface AgentUpdateRequest {
|
||||
name?: string;
|
||||
|
@ -208,8 +210,13 @@ export const UpdateAgentDialog = ({ agentId, isOpen, onOpenChange, onAgentUpdate
|
|||
<DialogHeader className="px-6 py-4 border-b flex-shrink-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<DialogTitle className="text-xl font-semibold">
|
||||
<DialogTitle className="text-xl font-semibold flex items-center gap-2">
|
||||
Edit Agent
|
||||
{(agent as any).current_version && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{(agent as any).current_version.version_name}
|
||||
</Badge>
|
||||
)}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm mt-1">
|
||||
Modify your agent's configuration and capabilities
|
||||
|
@ -281,6 +288,12 @@ export const UpdateAgentDialog = ({ agentId, isOpen, onOpenChange, onAgentUpdate
|
|||
<Sparkles className="h-4 w-4" />
|
||||
MCP Servers
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="versions"
|
||||
>
|
||||
<GitBranch className="h-4 w-4" />
|
||||
Versions
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="tools" className="flex-1 flex flex-col m-0 min-h-0">
|
||||
|
@ -372,37 +385,60 @@ export const UpdateAgentDialog = ({ agentId, isOpen, onOpenChange, onAgentUpdate
|
|||
onConfigurationChange={handleMCPConfigurationChange}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="versions" className="flex-1 m-0 p-6 overflow-y-auto">
|
||||
<AgentVersionManager
|
||||
agent={agent as any}
|
||||
onCreateVersion={() => {
|
||||
// When creating a new version, save current changes first
|
||||
handleSubmit();
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-6 border-t py-4 flex-shrink-0">
|
||||
<div className="flex justify-end gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCancel}
|
||||
disabled={updateAgentMutation.isPending}
|
||||
className="px-6"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={updateAgentMutation.isPending || !formData.name?.trim() || !formData.system_prompt?.trim()}
|
||||
>
|
||||
{updateAgentMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Saving Changes
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-4 w-4" />
|
||||
Save Changes
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<div className="space-y-3">
|
||||
{/* Show notice if changes will create a new version */}
|
||||
{agent && (formData.system_prompt !== agent.system_prompt ||
|
||||
JSON.stringify(formData.configured_mcps) !== JSON.stringify(agent.configured_mcps) ||
|
||||
JSON.stringify(formData.custom_mcps) !== JSON.stringify(agent.custom_mcps) ||
|
||||
JSON.stringify(formData.agentpress_tools) !== JSON.stringify(agent.agentpress_tools)) && (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground bg-muted/50 px-3 py-2 rounded-md">
|
||||
<GitBranch className="h-4 w-4" />
|
||||
<span>These changes will create a new version of your agent</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCancel}
|
||||
disabled={updateAgentMutation.isPending}
|
||||
className="px-6"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={updateAgentMutation.isPending || !formData.name?.trim() || !formData.system_prompt?.trim()}
|
||||
>
|
||||
{updateAgentMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Saving Changes
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-4 w-4" />
|
||||
Save Changes
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
|
|
@ -9,6 +9,21 @@ export interface FilterOptions {
|
|||
selectedTools: string[];
|
||||
}
|
||||
|
||||
export interface AgentVersion {
|
||||
version_id: string;
|
||||
agent_id: string;
|
||||
version_number: number;
|
||||
version_name: string;
|
||||
system_prompt: string;
|
||||
configured_mcps?: Array<{ name: string }>;
|
||||
custom_mcps?: Array<any>;
|
||||
agentpress_tools?: Record<string, any>;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by?: string;
|
||||
}
|
||||
|
||||
export interface Agent {
|
||||
agent_id: string;
|
||||
name: string;
|
||||
|
@ -18,6 +33,9 @@ export interface Agent {
|
|||
updated_at?: string;
|
||||
configured_mcps?: Array<{ name: string }>;
|
||||
agentpress_tools?: Record<string, any>;
|
||||
current_version_id?: string;
|
||||
version_count?: number;
|
||||
current_version?: AgentVersion;
|
||||
}
|
||||
|
||||
export interface MutationState {
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
const AGENT_EMOJIS = [
|
||||
'🤖', '🧠', '💡', '🚀', '⚡', '🔮', '🎯', '🛡️', '🔧', '🎨',
|
||||
'📊', '📈', '🔍', '🌟', '✨', '🎪', '🎭', '🎨', '🎯', '🎲',
|
||||
'🧩', '🔬', '🔭', '🗺️', '🧭', '⚙️', '🛠️', '🔩', '🔗', '📡',
|
||||
'🌐', '💻', '🖥️', '📱', '⌨️', '🖱️', '💾', '💿', '📀', '🗄️',
|
||||
'📂', '📁', '🗂️', '📋', '📌', '📍', '📎', '🖇️', '📏', '📐',
|
||||
'✂️', '🖊️', '🖋️', '✒️', '🖌️', '🖍️', '📝', '✏️', '🔐', '🔒',
|
||||
'🔓', '🔏', '🔑', '🗝️', '🔨', '⛏️', '⚒️', '🛡️', '🏹', '🎯',
|
||||
'🎰', '🎮', '🕹️', '🎲', '♠️', '♥️', '♦️', '♣️', '🃏', '🀄',
|
||||
'🎴', '🎭', '🖼️', '🎨', '🧵', '🧶', '🎸', '🎹', '🎺', '🎻',
|
||||
'🥁', '🎬', '🎤', '🎧', '🎼', '🎵', '🎶', '🎙️', '🎚️', '🎛️',
|
||||
'📻', '📺', '📷', '📹', '📽️', '🎞️', '📞', '☎️', '📟', '📠',
|
||||
'💎', '💍', '🏆', '🥇', '🥈', '🥉', '🏅', '🎖️', '🏵️', '🎗️',
|
||||
'🎫', '🎟️', '🎪', '🎭', '🎨', '🎬', '🎤', '🎧', '🎼', '🎹',
|
||||
'🦾', '🦿', '🦴', '👁️', '🧠', '🫀', '🫁', '🦷', '🦴', '👀'
|
||||
];
|
||||
|
||||
const AVATAR_COLORS = [
|
||||
'#FF6B6B', // Red
|
||||
'#4ECDC4', // Teal
|
||||
'#45B7D1', // Sky Blue
|
||||
'#96CEB4', // Mint Green
|
||||
'#FECA57', // Yellow
|
||||
'#FF9FF3', // Pink
|
||||
'#54A0FF', // Blue
|
||||
'#48DBFB', // Light Blue
|
||||
'#1DD1A1', // Emerald
|
||||
'#00D2D3', // Cyan
|
||||
'#5F27CD', // Purple
|
||||
'#341F97', // Dark Purple
|
||||
'#EE5A24', // Orange
|
||||
'#F368E0', // Magenta
|
||||
'#FF6348', // Coral
|
||||
'#7BED9F', // Light Green
|
||||
'#70A1FF', // Periwinkle
|
||||
'#5352ED', // Indigo
|
||||
'#3742FA', // Royal Blue
|
||||
'#2ED573', // Green
|
||||
'#1E90FF', // Dodger Blue
|
||||
'#FF1744', // Red Accent
|
||||
'#D500F9', // Purple Accent
|
||||
'#00E676', // Green Accent
|
||||
'#FF6D00', // Orange Accent
|
||||
'#00B8D4', // Cyan Accent
|
||||
'#6C5CE7', // Soft Purple
|
||||
'#A29BFE', // Lavender
|
||||
'#FD79A8', // Rose
|
||||
'#FDCB6E', // Mustard
|
||||
'#6C5CE7', // Violet
|
||||
'#00B894', // Mint
|
||||
'#00CEC9', // Turquoise
|
||||
'#0984E3', // Blue
|
||||
'#6C5CE7', // Purple
|
||||
'#A29BFE', // Light Purple
|
||||
'#74B9FF', // Light Blue
|
||||
'#81ECEC', // Light Cyan
|
||||
'#55A3FF', // Sky
|
||||
'#FD79A8', // Pink
|
||||
'#FDCB6E', // Yellow
|
||||
'#FF7675', // Light Red
|
||||
'#E17055', // Terra Cotta
|
||||
'#FAB1A0', // Peach
|
||||
'#74B9FF', // Powder Blue
|
||||
'#A29BFE', // Periwinkle
|
||||
'#DFE6E9', // Light Gray
|
||||
'#B2BEC3', // Gray
|
||||
'#636E72', // Dark Gray
|
||||
];
|
||||
|
||||
export function generateRandomEmoji(): string {
|
||||
const randomIndex = Math.floor(Math.random() * AGENT_EMOJIS.length);
|
||||
return AGENT_EMOJIS[randomIndex];
|
||||
}
|
||||
|
||||
export function generateRandomAvatarColor(): string {
|
||||
const randomIndex = Math.floor(Math.random() * AVATAR_COLORS.length);
|
||||
return AVATAR_COLORS[randomIndex];
|
||||
}
|
||||
|
||||
export function generateRandomAvatar(): { avatar: string; avatar_color: string } {
|
||||
return {
|
||||
avatar: generateRandomEmoji(),
|
||||
avatar_color: generateRandomAvatarColor(),
|
||||
};
|
||||
}
|
||||
|
||||
export function generateAvatarFromSeed(seed: string): { avatar: string; avatar_color: string } {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < seed.length; i++) {
|
||||
const char = seed.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
const emojiIndex = Math.abs(hash) % AGENT_EMOJIS.length;
|
||||
const colorIndex = Math.abs(hash >> 8) % AVATAR_COLORS.length;
|
||||
return {
|
||||
avatar: AGENT_EMOJIS[emojiIndex],
|
||||
avatar_color: AVATAR_COLORS[colorIndex],
|
||||
};
|
||||
}
|
|
@ -17,6 +17,7 @@ import { useRouter } from 'next/navigation';
|
|||
import { DEFAULT_AGENTPRESS_TOOLS } from './_data/tools';
|
||||
import { AgentsParams } from '@/hooks/react-query/agents/utils';
|
||||
import { useFeatureFlags } from '@/lib/feature-flags';
|
||||
import { generateRandomAvatar } from './_utils/_avatar-generator';
|
||||
|
||||
type ViewMode = 'grid' | 'list';
|
||||
type SortOption = 'name' | 'created_at' | 'updated_at' | 'tools_count';
|
||||
|
@ -156,10 +157,14 @@ export default function AgentsPage() {
|
|||
|
||||
const handleCreateNewAgent = async () => {
|
||||
try {
|
||||
const { avatar, avatar_color } = generateRandomAvatar();
|
||||
|
||||
const defaultAgentData = {
|
||||
name: 'New Agent',
|
||||
description: 'A newly created agent',
|
||||
system_prompt: 'You are a helpful assistant. Provide clear, accurate, and helpful responses to user queries.',
|
||||
avatar,
|
||||
avatar_color,
|
||||
configured_mcps: [],
|
||||
agentpress_tools: Object.fromEntries(
|
||||
Object.entries(DEFAULT_AGENTPRESS_TOOLS).map(([key, value]) => [
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Globe, GlobeLock, Download, Calendar, User, Tags, Loader2, AlertTriangle, Plus } from 'lucide-react';
|
||||
import { Globe, GlobeLock, Download, Calendar, User, Tags, Loader2, AlertTriangle, Plus, GitBranch } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
|
@ -66,21 +66,6 @@ export default function MyTemplatesPage() {
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Link href="/marketplace/secure">
|
||||
<Button variant="outline">
|
||||
<Globe className="h-4 w-4 mr-2" />
|
||||
Browse Marketplace
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/settings/credentials">
|
||||
<Button variant="outline">
|
||||
<User className="h-4 w-4 mr-2" />
|
||||
Manage Credentials
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
|
@ -128,32 +113,18 @@ export default function MyTemplatesPage() {
|
|||
<div className="text-4xl">
|
||||
{avatar}
|
||||
</div>
|
||||
<div className="absolute top-3 right-3 flex gap-2">
|
||||
{template.is_public ? (
|
||||
<div className="flex items-center gap-1 bg-green-500/20 backdrop-blur-sm px-2 py-1 rounded-full">
|
||||
<Globe className="h-3 w-3 text-green-400" />
|
||||
<span className="text-green-400 text-xs font-medium">Public</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 bg-gray-500/20 backdrop-blur-sm px-2 py-1 rounded-full">
|
||||
<GlobeLock className="h-3 w-3 text-gray-400" />
|
||||
<span className="text-gray-400 text-xs font-medium">Private</span>
|
||||
</div>
|
||||
)}
|
||||
{template.is_public && (
|
||||
<div className="flex items-center gap-1 bg-white/20 backdrop-blur-sm px-2 py-1 rounded-full">
|
||||
<Download className="h-3 w-3 text-white" />
|
||||
<span className="text-white text-xs font-medium">{template.download_count || 0}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 flex flex-col flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="text-foreground font-medium text-lg line-clamp-1 flex-1">
|
||||
{template.name}
|
||||
</h3>
|
||||
{template.metadata?.source_version_name && (
|
||||
<Badge variant="secondary" className="text-xs shrink-0">
|
||||
<GitBranch className="h-3 w-3" />
|
||||
{template.metadata.source_version_name}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm mb-3 line-clamp-2">
|
||||
{template.description || 'No description available'}
|
||||
|
@ -179,12 +150,6 @@ export default function MyTemplatesPage() {
|
|||
<Calendar className="h-3 w-3" />
|
||||
<span>Created {new Date(template.created_at).toLocaleDateString()}</span>
|
||||
</div>
|
||||
{template.marketplace_published_at && (
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<Globe className="h-3 w-3" />
|
||||
<span>Published {new Date(template.marketplace_published_at).toLocaleDateString()}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-auto">
|
||||
|
@ -198,12 +163,12 @@ export default function MyTemplatesPage() {
|
|||
>
|
||||
{isUnpublishing ? (
|
||||
<>
|
||||
<Loader2 className="h-3 w-3 animate-spin mr-2" />
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
Unpublishing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<GlobeLock className="h-3 w-3 mr-2" />
|
||||
<GlobeLock className="h-3 w-3" />
|
||||
Make Private
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Search, Download, Star, Calendar, User, Tags, TrendingUp, Shield, CheckCircle, Loader2, Settings, Wrench, AlertTriangle } from 'lucide-react';
|
||||
import { Search, Download, Star, Calendar, User, Tags, TrendingUp, Shield, CheckCircle, Loader2, Settings, Wrench, AlertTriangle, GitBranch } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
@ -40,6 +40,11 @@ interface MarketplaceTemplate {
|
|||
required_config: string[];
|
||||
custom_type?: 'sse' | 'http';
|
||||
}>;
|
||||
metadata?: {
|
||||
source_agent_id?: string;
|
||||
source_version_id?: string;
|
||||
source_version_name?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface SetupStep {
|
||||
|
@ -524,6 +529,7 @@ export default function MarketplacePage() {
|
|||
avatar_color: template.avatar_color,
|
||||
template_id: template.template_id,
|
||||
mcp_requirements: template.mcp_requirements,
|
||||
metadata: template.metadata,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -785,6 +791,12 @@ export default function MarketplacePage() {
|
|||
<h3 className="text-foreground font-medium text-lg line-clamp-1 flex-1">
|
||||
{item.name}
|
||||
</h3>
|
||||
{item.metadata?.source_version_name && (
|
||||
<Badge variant="secondary" className="text-xs shrink-0">
|
||||
<GitBranch className="h-3 w-3" />
|
||||
{item.metadata.source_version_name}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm mb-3 line-clamp-2">
|
||||
{item.description || 'No description available'}
|
||||
|
|
|
@ -141,9 +141,6 @@ export function SidebarLeft({
|
|||
<Bot className="h-4 w-4 mr-2" />
|
||||
<span className="flex items-center justify-between w-full">
|
||||
Agent Playground
|
||||
<Badge variant="new">
|
||||
New
|
||||
</Badge>
|
||||
</span>
|
||||
</SidebarMenuButton>
|
||||
</Link>
|
||||
|
@ -156,9 +153,6 @@ export function SidebarLeft({
|
|||
<Store className="h-4 w-4 mr-2" />
|
||||
<span className="flex items-center justify-between w-full">
|
||||
Marketplace
|
||||
<Badge variant="new">
|
||||
New
|
||||
</Badge>
|
||||
</span>
|
||||
</SidebarMenuButton>
|
||||
</Link>
|
||||
|
@ -171,9 +165,6 @@ export function SidebarLeft({
|
|||
<Key className="h-4 w-4 mr-2" />
|
||||
<span className="flex items-center justify-between w-full">
|
||||
Credentials
|
||||
<Badge variant="new">
|
||||
New
|
||||
</Badge>
|
||||
</span>
|
||||
</SidebarMenuButton>
|
||||
</Link>
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
getAgentVersions,
|
||||
createAgentVersion,
|
||||
activateAgentVersion,
|
||||
getAgentVersion,
|
||||
AgentVersion,
|
||||
AgentVersionCreateRequest
|
||||
} from './utils';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export const useAgentVersions = (agentId: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['agent-versions', agentId],
|
||||
queryFn: () => getAgentVersions(agentId),
|
||||
enabled: !!agentId,
|
||||
});
|
||||
};
|
||||
|
||||
export const useAgentVersion = (agentId: string, versionId: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['agent-version', agentId, versionId],
|
||||
queryFn: () => getAgentVersion(agentId, versionId),
|
||||
enabled: !!agentId && !!versionId,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateAgentVersion = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ agentId, data }: { agentId: string; data: AgentVersionCreateRequest }) =>
|
||||
createAgentVersion(agentId, data),
|
||||
onSuccess: (newVersion, { agentId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['agent-versions', agentId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['agent', agentId] });
|
||||
toast.success(`Created version ${newVersion.version_name}`);
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || 'Failed to create version');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useActivateAgentVersion = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ agentId, versionId }: { agentId: string; versionId: string }) =>
|
||||
activateAgentVersion(agentId, versionId),
|
||||
onSuccess: (_, { agentId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['agent', agentId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['agent-versions', agentId] });
|
||||
toast.success('Version activated successfully');
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || 'Failed to activate version');
|
||||
},
|
||||
});
|
||||
};
|
|
@ -79,6 +79,36 @@ export type AgentCreateRequest = {
|
|||
is_default?: boolean;
|
||||
};
|
||||
|
||||
export type AgentVersionCreateRequest = {
|
||||
system_prompt: string;
|
||||
configured_mcps?: Array<{
|
||||
name: string;
|
||||
config: Record<string, any>;
|
||||
}>;
|
||||
custom_mcps?: Array<{
|
||||
name: string;
|
||||
type: 'json' | 'sse';
|
||||
config: Record<string, any>;
|
||||
enabledTools: string[];
|
||||
}>;
|
||||
agentpress_tools?: Record<string, any>;
|
||||
};
|
||||
|
||||
export type AgentVersion = {
|
||||
version_id: string;
|
||||
agent_id: string;
|
||||
version_number: number;
|
||||
version_name: string;
|
||||
system_prompt: string;
|
||||
configured_mcps: Array<any>;
|
||||
custom_mcps: Array<any>;
|
||||
agentpress_tools: Record<string, any>;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by?: string;
|
||||
};
|
||||
|
||||
export type AgentUpdateRequest = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
|
@ -458,4 +488,153 @@ export const startAgentBuilderChat = async (
|
|||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const getAgentVersions = async (agentId: string): Promise<AgentVersion[]> => {
|
||||
try {
|
||||
const agentPlaygroundEnabled = await isFlagEnabled('custom_agents');
|
||||
if (!agentPlaygroundEnabled) {
|
||||
throw new Error('Custom agents is not enabled');
|
||||
}
|
||||
const supabase = createClient();
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
|
||||
if (!session) {
|
||||
throw new Error('You must be logged in to get agent versions');
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/agents/${agentId}/versions`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${session.access_token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
||||
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const versions = await response.json();
|
||||
console.log('[API] Fetched agent versions:', agentId, versions.length);
|
||||
return versions;
|
||||
} catch (err) {
|
||||
console.error('Error fetching agent versions:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const createAgentVersion = async (
|
||||
agentId: string,
|
||||
data: AgentVersionCreateRequest
|
||||
): Promise<AgentVersion> => {
|
||||
try {
|
||||
const agentPlaygroundEnabled = await isFlagEnabled('custom_agents');
|
||||
if (!agentPlaygroundEnabled) {
|
||||
throw new Error('Custom agents is not enabled');
|
||||
}
|
||||
const supabase = createClient();
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
|
||||
if (!session) {
|
||||
throw new Error('You must be logged in to create agent version');
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/agents/${agentId}/versions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${session.access_token}`,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
||||
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const version = await response.json();
|
||||
console.log('[API] Created agent version:', version.version_id);
|
||||
return version;
|
||||
} catch (err) {
|
||||
console.error('Error creating agent version:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const activateAgentVersion = async (
|
||||
agentId: string,
|
||||
versionId: string
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const agentPlaygroundEnabled = await isFlagEnabled('custom_agents');
|
||||
if (!agentPlaygroundEnabled) {
|
||||
throw new Error('Custom agents is not enabled');
|
||||
}
|
||||
const supabase = createClient();
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
|
||||
if (!session) {
|
||||
throw new Error('You must be logged in to activate agent version');
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${API_URL}/agents/${agentId}/versions/${versionId}/activate`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${session.access_token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
||||
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
console.log('[API] Activated agent version:', versionId);
|
||||
} catch (err) {
|
||||
console.error('Error activating agent version:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const getAgentVersion = async (
|
||||
agentId: string,
|
||||
versionId: string
|
||||
): Promise<AgentVersion> => {
|
||||
try {
|
||||
const agentPlaygroundEnabled = await isFlagEnabled('custom_agents');
|
||||
if (!agentPlaygroundEnabled) {
|
||||
throw new Error('Custom agents is not enabled');
|
||||
}
|
||||
const supabase = createClient();
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
|
||||
if (!session) {
|
||||
throw new Error('You must be logged in to get agent version');
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${API_URL}/agents/${agentId}/versions/${versionId}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${session.access_token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
||||
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const version = await response.json();
|
||||
console.log('[API] Fetched agent version:', version.version_id);
|
||||
return version;
|
||||
} catch (err) {
|
||||
console.error('Error fetching agent version:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
|
@ -44,6 +44,11 @@ export interface AgentTemplate {
|
|||
creator_name?: string;
|
||||
avatar?: string;
|
||||
avatar_color?: string;
|
||||
metadata?: {
|
||||
source_agent_id?: string;
|
||||
source_version_id?: string;
|
||||
source_version_name?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MCPRequirement {
|
||||
|
|
Loading…
Reference in New Issue