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: Optional[str] = None
|
||||||
avatar_color: 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):
|
class AgentUpdateRequest(BaseModel):
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
|
@ -71,20 +91,23 @@ class AgentResponse(BaseModel):
|
||||||
agent_id: str
|
agent_id: str
|
||||||
account_id: str
|
account_id: str
|
||||||
name: str
|
name: str
|
||||||
description: Optional[str]
|
description: Optional[str] = None
|
||||||
system_prompt: str
|
system_prompt: str
|
||||||
configured_mcps: List[Dict[str, Any]]
|
configured_mcps: List[Dict[str, Any]]
|
||||||
custom_mcps: Optional[List[Dict[str, Any]]] = []
|
custom_mcps: List[Dict[str, Any]]
|
||||||
agentpress_tools: Dict[str, Any]
|
agentpress_tools: Dict[str, Any]
|
||||||
is_default: bool
|
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
|
is_public: Optional[bool] = False
|
||||||
marketplace_published_at: Optional[str] = None
|
marketplace_published_at: Optional[str] = None
|
||||||
download_count: Optional[int] = 0
|
download_count: Optional[int] = 0
|
||||||
tags: Optional[List[str]] = []
|
tags: Optional[List[str]] = []
|
||||||
avatar: Optional[str]
|
current_version_id: Optional[str] = None
|
||||||
avatar_color: Optional[str]
|
version_count: Optional[int] = 1
|
||||||
created_at: str
|
current_version: Optional[AgentVersionResponse] = None
|
||||||
updated_at: str
|
|
||||||
|
|
||||||
class PaginationInfo(BaseModel):
|
class PaginationInfo(BaseModel):
|
||||||
page: int
|
page: int
|
||||||
|
@ -387,12 +410,13 @@ async def start_agent(
|
||||||
if is_agent_builder:
|
if is_agent_builder:
|
||||||
logger.info(f"Thread {thread_id} is in agent builder mode, target_agent_id: {target_agent_id}")
|
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
|
agent_config = None
|
||||||
effective_agent_id = body.agent_id or thread_agent_id # Use provided agent_id or the one stored in thread
|
effective_agent_id = body.agent_id or thread_agent_id # Use provided agent_id or the one stored in thread
|
||||||
|
|
||||||
if effective_agent_id:
|
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 not agent_result.data:
|
||||||
if body.agent_id:
|
if body.agent_id:
|
||||||
raise HTTPException(status_code=404, detail="Agent not found or access denied")
|
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")
|
logger.warning(f"Stored agent_id {effective_agent_id} not found, falling back to default")
|
||||||
effective_agent_id = None
|
effective_agent_id = None
|
||||||
else:
|
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"
|
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 no agent found yet, try to get default agent for the account
|
||||||
if not agent_config:
|
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:
|
if default_agent_result.data:
|
||||||
agent_config = default_agent_result.data[0]
|
agent_data = default_agent_result.data[0]
|
||||||
logger.info(f"Using default agent: {agent_config['name']} ({agent_config['agent_id']})")
|
# 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
|
# 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:
|
if body.agent_id and body.agent_id != thread_agent_id and agent_config:
|
||||||
|
@ -1092,8 +1153,8 @@ async def get_agents(
|
||||||
# Calculate offset
|
# Calculate offset
|
||||||
offset = (page - 1) * limit
|
offset = (page - 1) * limit
|
||||||
|
|
||||||
# Start building the query
|
# Start building the query - include version data
|
||||||
query = client.table('agents').select('*', count='exact').eq("account_id", user_id)
|
query = client.table('agents').select('*, agent_versions!current_version_id(*)', count='exact').eq("account_id", user_id)
|
||||||
|
|
||||||
# Apply search filter
|
# Apply search filter
|
||||||
if search:
|
if search:
|
||||||
|
@ -1206,6 +1267,24 @@ async def get_agents(
|
||||||
# Format the response
|
# Format the response
|
||||||
agent_list = []
|
agent_list = []
|
||||||
for agent in agents_data:
|
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_list.append(AgentResponse(
|
||||||
agent_id=agent['agent_id'],
|
agent_id=agent['agent_id'],
|
||||||
account_id=agent['account_id'],
|
account_id=agent['account_id'],
|
||||||
|
@ -1223,7 +1302,10 @@ async def get_agents(
|
||||||
avatar=agent.get('avatar'),
|
avatar=agent.get('avatar'),
|
||||||
avatar_color=agent.get('avatar_color'),
|
avatar_color=agent.get('avatar_color'),
|
||||||
created_at=agent['created_at'],
|
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
|
total_pages = (total_count + limit - 1) // limit
|
||||||
|
@ -1245,7 +1327,7 @@ async def get_agents(
|
||||||
|
|
||||||
@router.get("/agents/{agent_id}", response_model=AgentResponse)
|
@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)):
|
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"):
|
if not await is_enabled("custom_agents"):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=403,
|
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
|
client = await db.client
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get agent with access check - only owner or public agents
|
# Get agent with current version data
|
||||||
agent = await client.table('agents').select('*').eq("agent_id", agent_id).execute()
|
agent = await client.table('agents').select('*, agent_versions!current_version_id(*)').eq("agent_id", agent_id).execute()
|
||||||
|
|
||||||
if not agent.data:
|
if not agent.data:
|
||||||
raise HTTPException(status_code=404, detail="Agent not found")
|
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):
|
if agent_data['account_id'] != user_id and not agent_data.get('is_public', False):
|
||||||
raise HTTPException(status_code=403, detail="Access denied")
|
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(
|
return AgentResponse(
|
||||||
agent_id=agent_data['agent_id'],
|
agent_id=agent_data['agent_id'],
|
||||||
account_id=agent_data['account_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=agent_data.get('avatar'),
|
||||||
avatar_color=agent_data.get('avatar_color'),
|
avatar_color=agent_data.get('avatar_color'),
|
||||||
created_at=agent_data['created_at'],
|
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:
|
except HTTPException:
|
||||||
|
@ -1299,7 +1403,7 @@ async def create_agent(
|
||||||
agent_data: AgentCreateRequest,
|
agent_data: AgentCreateRequest,
|
||||||
user_id: str = Depends(get_current_user_id_from_jwt)
|
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}")
|
logger.info(f"Creating new agent for user: {user_id}")
|
||||||
if not await is_enabled("custom_agents"):
|
if not await is_enabled("custom_agents"):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
@ -1313,12 +1417,7 @@ async def create_agent(
|
||||||
if agent_data.is_default:
|
if agent_data.is_default:
|
||||||
await client.table('agents').update({"is_default": False}).eq("account_id", user_id).eq("is_default", True).execute()
|
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(
|
# Create the agent
|
||||||
# agent_name=agent_data.name,
|
|
||||||
# description=agent_data.description or "",
|
|
||||||
# user_system_prompt=agent_data.system_prompt
|
|
||||||
# )
|
|
||||||
|
|
||||||
insert_data = {
|
insert_data = {
|
||||||
"account_id": user_id,
|
"account_id": user_id,
|
||||||
"name": agent_data.name,
|
"name": agent_data.name,
|
||||||
|
@ -1329,7 +1428,8 @@ async def create_agent(
|
||||||
"agentpress_tools": agent_data.agentpress_tools or {},
|
"agentpress_tools": agent_data.agentpress_tools or {},
|
||||||
"is_default": agent_data.is_default or False,
|
"is_default": agent_data.is_default or False,
|
||||||
"avatar": agent_data.avatar,
|
"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()
|
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")
|
raise HTTPException(status_code=500, detail="Failed to create agent")
|
||||||
|
|
||||||
agent = new_agent.data[0]
|
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(
|
return AgentResponse(
|
||||||
agent_id=agent['agent_id'],
|
agent_id=agent['agent_id'],
|
||||||
|
@ -1357,7 +1492,10 @@ async def create_agent(
|
||||||
avatar=agent.get('avatar'),
|
avatar=agent.get('avatar'),
|
||||||
avatar_color=agent.get('avatar_color'),
|
avatar_color=agent.get('avatar_color'),
|
||||||
created_at=agent['created_at'],
|
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:
|
except HTTPException:
|
||||||
|
@ -1372,7 +1510,7 @@ async def update_agent(
|
||||||
agent_data: AgentUpdateRequest,
|
agent_data: AgentUpdateRequest,
|
||||||
user_id: str = Depends(get_current_user_id_from_jwt)
|
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"):
|
if not await is_enabled("custom_agents"):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=403,
|
status_code=403,
|
||||||
|
@ -1382,34 +1520,41 @@ async def update_agent(
|
||||||
client = await db.client
|
client = await db.client
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# First verify the agent exists and belongs to the user
|
# First verify the agent exists and belongs to the user, get with current version
|
||||||
existing_agent = await client.table('agents').select('*').eq("agent_id", agent_id).eq("account_id", user_id).maybe_single().execute()
|
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:
|
if not existing_agent.data:
|
||||||
raise HTTPException(status_code=404, detail="Agent not found")
|
raise HTTPException(status_code=404, detail="Agent not found")
|
||||||
|
|
||||||
existing_data = existing_agent.data
|
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 = {}
|
update_data = {}
|
||||||
if agent_data.name is not None:
|
if agent_data.name is not None:
|
||||||
update_data["name"] = agent_data.name
|
update_data["name"] = agent_data.name
|
||||||
if agent_data.description is not None:
|
if agent_data.description is not None:
|
||||||
update_data["description"] = agent_data.description
|
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:
|
if agent_data.is_default is not None:
|
||||||
update_data["is_default"] = agent_data.is_default
|
update_data["is_default"] = agent_data.is_default
|
||||||
# If setting as default, unset other defaults first
|
# If setting as default, unset other defaults first
|
||||||
|
@ -1420,23 +1565,89 @@ async def update_agent(
|
||||||
if agent_data.avatar_color is not None:
|
if agent_data.avatar_color is not None:
|
||||||
update_data["avatar_color"] = agent_data.avatar_color
|
update_data["avatar_color"] = agent_data.avatar_color
|
||||||
|
|
||||||
if not update_data:
|
# Also update the agent table with the latest values (for backward compatibility)
|
||||||
# No fields to update, return existing agent
|
if agent_data.system_prompt is not None:
|
||||||
agent = existing_agent.data
|
update_data["system_prompt"] = agent_data.system_prompt
|
||||||
else:
|
if agent_data.configured_mcps is not None:
|
||||||
# Update the agent
|
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()
|
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:
|
if not update_result.data:
|
||||||
raise HTTPException(status_code=500, detail="Failed to update agent")
|
raise HTTPException(status_code=500, detail="Failed to update agent")
|
||||||
|
|
||||||
# Fetch the updated agent data
|
# Fetch the updated agent data with version info
|
||||||
updated_agent = await client.table('agents').select('*').eq("agent_id", agent_id).eq("account_id", user_id).maybe_single().execute()
|
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:
|
if not updated_agent.data:
|
||||||
raise HTTPException(status_code=500, detail="Failed to fetch updated agent")
|
raise HTTPException(status_code=500, detail="Failed to fetch updated agent")
|
||||||
|
|
||||||
agent = updated_agent.data
|
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}")
|
logger.info(f"Updated agent {agent_id} for user: {user_id}")
|
||||||
|
|
||||||
|
@ -1457,7 +1668,10 @@ async def update_agent(
|
||||||
avatar=agent.get('avatar'),
|
avatar=agent.get('avatar'),
|
||||||
avatar_color=agent.get('avatar_color'),
|
avatar_color=agent.get('avatar_color'),
|
||||||
created_at=agent['created_at'],
|
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:
|
except HTTPException:
|
||||||
|
@ -1820,3 +2034,147 @@ async def get_agent_builder_chat_history(
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error fetching agent builder chat history for agent {agent_id}: {str(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)}")
|
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
|
updated_at: datetime
|
||||||
avatar: Optional[str]
|
avatar: Optional[str]
|
||||||
avatar_color: Optional[str]
|
avatar_color: Optional[str]
|
||||||
|
metadata: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -86,8 +87,8 @@ class TemplateManager:
|
||||||
try:
|
try:
|
||||||
client = await db.client
|
client = await db.client
|
||||||
|
|
||||||
# Get the existing agent
|
# Get the existing agent with current version
|
||||||
agent_result = await client.table('agents').select('*').eq('agent_id', agent_id).execute()
|
agent_result = await client.table('agents').select('*, agent_versions!current_version_id(*)').eq('agent_id', agent_id).execute()
|
||||||
if not agent_result.data:
|
if not agent_result.data:
|
||||||
raise ValueError("Agent not found")
|
raise ValueError("Agent not found")
|
||||||
|
|
||||||
|
@ -122,18 +123,34 @@ class TemplateManager:
|
||||||
logger.info(f"Created custom MCP requirement: {requirement}")
|
logger.info(f"Created custom MCP requirement: {requirement}")
|
||||||
mcp_requirements.append(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
|
# Create template
|
||||||
template_data = {
|
template_data = {
|
||||||
'creator_id': creator_id,
|
'creator_id': creator_id,
|
||||||
'name': agent['name'],
|
'name': agent['name'],
|
||||||
'description': agent.get('description'),
|
'description': agent.get('description'),
|
||||||
'system_prompt': agent['system_prompt'],
|
'system_prompt': system_prompt,
|
||||||
'mcp_requirements': mcp_requirements,
|
'mcp_requirements': mcp_requirements,
|
||||||
'agentpress_tools': agent.get('agentpress_tools', {}),
|
'agentpress_tools': agentpress_tools,
|
||||||
'tags': tags or [],
|
'tags': tags or [],
|
||||||
'is_public': make_public,
|
'is_public': make_public,
|
||||||
'avatar': agent.get('avatar'),
|
'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:
|
if make_public:
|
||||||
|
@ -192,7 +209,8 @@ class TemplateManager:
|
||||||
created_at=template_data['created_at'],
|
created_at=template_data['created_at'],
|
||||||
updated_at=template_data['updated_at'],
|
updated_at=template_data['updated_at'],
|
||||||
avatar=template_data.get('avatar'),
|
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:
|
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 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 { Button } from '@/components/ui/button';
|
||||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
|
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';
|
import { Dialog, DialogContent, DialogTitle, DialogHeader, DialogDescription } from '@/components/ui/dialog';
|
||||||
|
@ -26,6 +26,13 @@ interface Agent {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
avatar_color?: string;
|
avatar_color?: string;
|
||||||
template_id?: string;
|
template_id?: string;
|
||||||
|
current_version_id?: string;
|
||||||
|
version_count?: number;
|
||||||
|
current_version?: {
|
||||||
|
version_id: string;
|
||||||
|
version_name: string;
|
||||||
|
version_number: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AgentsGridProps {
|
interface AgentsGridProps {
|
||||||
|
@ -76,6 +83,12 @@ const AgentModal = ({ agent, isOpen, onClose, onCustomize, onChat, onPublish, on
|
||||||
<h2 className="text-xl font-semibold text-foreground">
|
<h2 className="text-xl font-semibold text-foreground">
|
||||||
{agent.name}
|
{agent.name}
|
||||||
</h2>
|
</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 && (
|
{agent.is_public && (
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs">
|
||||||
<Shield className="h-3 w-3" />
|
<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">
|
<h3 className="text-foreground font-medium text-lg line-clamp-1 flex-1">
|
||||||
{agent.name}
|
{agent.name}
|
||||||
</h3>
|
</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 && (
|
{agent.is_public && (
|
||||||
<Badge variant="outline" className="text-xs shrink-0">
|
<Badge variant="outline" className="text-xs shrink-0">
|
||||||
<Shield className="h-3 w-3" />
|
<Shield className="h-3 w-3" />
|
||||||
|
|
|
@ -6,13 +6,15 @@ import { Textarea } from '@/components/ui/textarea';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
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 { toast } from 'sonner';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { DEFAULT_AGENTPRESS_TOOLS, getToolDisplayName } from '../_data/tools';
|
import { DEFAULT_AGENTPRESS_TOOLS, getToolDisplayName } from '../_data/tools';
|
||||||
import { useAgent, useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
|
import { useAgent, useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
|
||||||
import { MCPConfiguration } from './mcp-configuration';
|
import { MCPConfiguration } from './mcp-configuration';
|
||||||
import { MCPConfigurationNew } from './mcp/mcp-configuration-new';
|
import { MCPConfigurationNew } from './mcp/mcp-configuration-new';
|
||||||
|
import { AgentVersionManager } from './AgentVersionManager';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
interface AgentUpdateRequest {
|
interface AgentUpdateRequest {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
@ -208,8 +210,13 @@ export const UpdateAgentDialog = ({ agentId, isOpen, onOpenChange, onAgentUpdate
|
||||||
<DialogHeader className="px-6 py-4 border-b flex-shrink-0">
|
<DialogHeader className="px-6 py-4 border-b flex-shrink-0">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<DialogTitle className="text-xl font-semibold">
|
<DialogTitle className="text-xl font-semibold flex items-center gap-2">
|
||||||
Edit Agent
|
Edit Agent
|
||||||
|
{(agent as any).current_version && (
|
||||||
|
<Badge variant="secondary" className="text-xs">
|
||||||
|
{(agent as any).current_version.version_name}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-sm mt-1">
|
<DialogDescription className="text-sm mt-1">
|
||||||
Modify your agent's configuration and capabilities
|
Modify your agent's configuration and capabilities
|
||||||
|
@ -281,6 +288,12 @@ export const UpdateAgentDialog = ({ agentId, isOpen, onOpenChange, onAgentUpdate
|
||||||
<Sparkles className="h-4 w-4" />
|
<Sparkles className="h-4 w-4" />
|
||||||
MCP Servers
|
MCP Servers
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger
|
||||||
|
value="versions"
|
||||||
|
>
|
||||||
|
<GitBranch className="h-4 w-4" />
|
||||||
|
Versions
|
||||||
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="tools" className="flex-1 flex flex-col m-0 min-h-0">
|
<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}
|
onConfigurationChange={handleMCPConfigurationChange}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</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>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-6 border-t py-4 flex-shrink-0">
|
<div className="px-6 border-t py-4 flex-shrink-0">
|
||||||
<div className="flex justify-end gap-3">
|
<div className="space-y-3">
|
||||||
<Button
|
{/* Show notice if changes will create a new version */}
|
||||||
variant="outline"
|
{agent && (formData.system_prompt !== agent.system_prompt ||
|
||||||
onClick={handleCancel}
|
JSON.stringify(formData.configured_mcps) !== JSON.stringify(agent.configured_mcps) ||
|
||||||
disabled={updateAgentMutation.isPending}
|
JSON.stringify(formData.custom_mcps) !== JSON.stringify(agent.custom_mcps) ||
|
||||||
className="px-6"
|
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">
|
||||||
Cancel
|
<GitBranch className="h-4 w-4" />
|
||||||
</Button>
|
<span>These changes will create a new version of your agent</span>
|
||||||
<Button
|
</div>
|
||||||
onClick={handleSubmit}
|
)}
|
||||||
disabled={updateAgentMutation.isPending || !formData.name?.trim() || !formData.system_prompt?.trim()}
|
|
||||||
>
|
<div className="flex justify-end gap-3">
|
||||||
{updateAgentMutation.isPending ? (
|
<Button
|
||||||
<>
|
variant="outline"
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
onClick={handleCancel}
|
||||||
Saving Changes
|
disabled={updateAgentMutation.isPending}
|
||||||
</>
|
className="px-6"
|
||||||
) : (
|
>
|
||||||
<>
|
Cancel
|
||||||
<Save className="h-4 w-4" />
|
</Button>
|
||||||
Save Changes
|
<Button
|
||||||
</>
|
onClick={handleSubmit}
|
||||||
)}
|
disabled={updateAgentMutation.isPending || !formData.name?.trim() || !formData.system_prompt?.trim()}
|
||||||
</Button>
|
>
|
||||||
|
{updateAgentMutation.isPending ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
Saving Changes
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Save className="h-4 w-4" />
|
||||||
|
Save Changes
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
@ -9,6 +9,21 @@ export interface FilterOptions {
|
||||||
selectedTools: string[];
|
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 {
|
export interface Agent {
|
||||||
agent_id: string;
|
agent_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -18,6 +33,9 @@ export interface Agent {
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
configured_mcps?: Array<{ name: string }>;
|
configured_mcps?: Array<{ name: string }>;
|
||||||
agentpress_tools?: Record<string, any>;
|
agentpress_tools?: Record<string, any>;
|
||||||
|
current_version_id?: string;
|
||||||
|
version_count?: number;
|
||||||
|
current_version?: AgentVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MutationState {
|
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 { DEFAULT_AGENTPRESS_TOOLS } from './_data/tools';
|
||||||
import { AgentsParams } from '@/hooks/react-query/agents/utils';
|
import { AgentsParams } from '@/hooks/react-query/agents/utils';
|
||||||
import { useFeatureFlags } from '@/lib/feature-flags';
|
import { useFeatureFlags } from '@/lib/feature-flags';
|
||||||
|
import { generateRandomAvatar } from './_utils/_avatar-generator';
|
||||||
|
|
||||||
type ViewMode = 'grid' | 'list';
|
type ViewMode = 'grid' | 'list';
|
||||||
type SortOption = 'name' | 'created_at' | 'updated_at' | 'tools_count';
|
type SortOption = 'name' | 'created_at' | 'updated_at' | 'tools_count';
|
||||||
|
@ -156,10 +157,14 @@ export default function AgentsPage() {
|
||||||
|
|
||||||
const handleCreateNewAgent = async () => {
|
const handleCreateNewAgent = async () => {
|
||||||
try {
|
try {
|
||||||
|
const { avatar, avatar_color } = generateRandomAvatar();
|
||||||
|
|
||||||
const defaultAgentData = {
|
const defaultAgentData = {
|
||||||
name: 'New Agent',
|
name: 'New Agent',
|
||||||
description: 'A newly created agent',
|
description: 'A newly created agent',
|
||||||
system_prompt: 'You are a helpful assistant. Provide clear, accurate, and helpful responses to user queries.',
|
system_prompt: 'You are a helpful assistant. Provide clear, accurate, and helpful responses to user queries.',
|
||||||
|
avatar,
|
||||||
|
avatar_color,
|
||||||
configured_mcps: [],
|
configured_mcps: [],
|
||||||
agentpress_tools: Object.fromEntries(
|
agentpress_tools: Object.fromEntries(
|
||||||
Object.entries(DEFAULT_AGENTPRESS_TOOLS).map(([key, value]) => [
|
Object.entries(DEFAULT_AGENTPRESS_TOOLS).map(([key, value]) => [
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
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 { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
@ -66,21 +66,6 @@ export default function MyTemplatesPage() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
@ -128,32 +113,18 @@ export default function MyTemplatesPage() {
|
||||||
<div className="text-4xl">
|
<div className="text-4xl">
|
||||||
{avatar}
|
{avatar}
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div className="p-4 flex flex-col flex-1">
|
<div className="p-4 flex flex-col flex-1">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<h3 className="text-foreground font-medium text-lg line-clamp-1 flex-1">
|
<h3 className="text-foreground font-medium text-lg line-clamp-1 flex-1">
|
||||||
{template.name}
|
{template.name}
|
||||||
</h3>
|
</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>
|
</div>
|
||||||
<p className="text-muted-foreground text-sm mb-3 line-clamp-2">
|
<p className="text-muted-foreground text-sm mb-3 line-clamp-2">
|
||||||
{template.description || 'No description available'}
|
{template.description || 'No description available'}
|
||||||
|
@ -179,12 +150,6 @@ export default function MyTemplatesPage() {
|
||||||
<Calendar className="h-3 w-3" />
|
<Calendar className="h-3 w-3" />
|
||||||
<span>Created {new Date(template.created_at).toLocaleDateString()}</span>
|
<span>Created {new Date(template.created_at).toLocaleDateString()}</span>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div className="mt-auto">
|
<div className="mt-auto">
|
||||||
|
@ -198,12 +163,12 @@ export default function MyTemplatesPage() {
|
||||||
>
|
>
|
||||||
{isUnpublishing ? (
|
{isUnpublishing ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="h-3 w-3 animate-spin mr-2" />
|
<Loader2 className="h-3 w-3 animate-spin" />
|
||||||
Unpublishing...
|
Unpublishing...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<GlobeLock className="h-3 w-3 mr-2" />
|
<GlobeLock className="h-3 w-3" />
|
||||||
Make Private
|
Make Private
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useMemo } from 'react';
|
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 { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
@ -40,6 +40,11 @@ interface MarketplaceTemplate {
|
||||||
required_config: string[];
|
required_config: string[];
|
||||||
custom_type?: 'sse' | 'http';
|
custom_type?: 'sse' | 'http';
|
||||||
}>;
|
}>;
|
||||||
|
metadata?: {
|
||||||
|
source_agent_id?: string;
|
||||||
|
source_version_id?: string;
|
||||||
|
source_version_name?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SetupStep {
|
interface SetupStep {
|
||||||
|
@ -524,6 +529,7 @@ export default function MarketplacePage() {
|
||||||
avatar_color: template.avatar_color,
|
avatar_color: template.avatar_color,
|
||||||
template_id: template.template_id,
|
template_id: template.template_id,
|
||||||
mcp_requirements: template.mcp_requirements,
|
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">
|
<h3 className="text-foreground font-medium text-lg line-clamp-1 flex-1">
|
||||||
{item.name}
|
{item.name}
|
||||||
</h3>
|
</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>
|
</div>
|
||||||
<p className="text-muted-foreground text-sm mb-3 line-clamp-2">
|
<p className="text-muted-foreground text-sm mb-3 line-clamp-2">
|
||||||
{item.description || 'No description available'}
|
{item.description || 'No description available'}
|
||||||
|
|
|
@ -141,9 +141,6 @@ export function SidebarLeft({
|
||||||
<Bot className="h-4 w-4 mr-2" />
|
<Bot className="h-4 w-4 mr-2" />
|
||||||
<span className="flex items-center justify-between w-full">
|
<span className="flex items-center justify-between w-full">
|
||||||
Agent Playground
|
Agent Playground
|
||||||
<Badge variant="new">
|
|
||||||
New
|
|
||||||
</Badge>
|
|
||||||
</span>
|
</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -156,9 +153,6 @@ export function SidebarLeft({
|
||||||
<Store className="h-4 w-4 mr-2" />
|
<Store className="h-4 w-4 mr-2" />
|
||||||
<span className="flex items-center justify-between w-full">
|
<span className="flex items-center justify-between w-full">
|
||||||
Marketplace
|
Marketplace
|
||||||
<Badge variant="new">
|
|
||||||
New
|
|
||||||
</Badge>
|
|
||||||
</span>
|
</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -171,9 +165,6 @@ export function SidebarLeft({
|
||||||
<Key className="h-4 w-4 mr-2" />
|
<Key className="h-4 w-4 mr-2" />
|
||||||
<span className="flex items-center justify-between w-full">
|
<span className="flex items-center justify-between w-full">
|
||||||
Credentials
|
Credentials
|
||||||
<Badge variant="new">
|
|
||||||
New
|
|
||||||
</Badge>
|
|
||||||
</span>
|
</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</Link>
|
</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;
|
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 = {
|
export type AgentUpdateRequest = {
|
||||||
name?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
@ -458,4 +488,153 @@ export const startAgentBuilderChat = async (
|
||||||
throw err;
|
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;
|
creator_name?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
avatar_color?: string;
|
avatar_color?: string;
|
||||||
|
metadata?: {
|
||||||
|
source_agent_id?: string;
|
||||||
|
source_version_id?: string;
|
||||||
|
source_version_name?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MCPRequirement {
|
export interface MCPRequirement {
|
||||||
|
|
Loading…
Reference in New Issue