Merge pull request #855 from escapade-mckv/ux-improvements

chore(ui): sync custom agents config with credentials profile
This commit is contained in:
Bobbie 2025-06-28 16:43:46 +05:30 committed by GitHub
commit 8a0a8f37b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 255 additions and 57 deletions

View File

@ -1658,32 +1658,83 @@ async def update_agent(
client = await db.client
try:
# First verify the agent exists and belongs to the user, get with current version
existing_agent = await client.table('agents').select('*, agent_versions!current_version_id(*)').eq("agent_id", agent_id).eq("account_id", user_id).maybe_single().execute()
if not existing_agent.data:
raise HTTPException(status_code=404, detail="Agent not found")
existing_data = existing_agent.data
current_version_data = existing_data.get('agent_versions', {})
current_version_data = existing_data.get('agent_versions')
if current_version_data is None:
logger.info(f"Agent {agent_id} has no version data, creating initial version")
try:
initial_version_data = {
"agent_id": agent_id,
"version_number": 1,
"version_name": "v1",
"system_prompt": existing_data.get('system_prompt', ''),
"configured_mcps": existing_data.get('configured_mcps', []),
"custom_mcps": existing_data.get('custom_mcps', []),
"agentpress_tools": existing_data.get('agentpress_tools', {}),
"is_active": True,
"created_by": user_id
}
version_result = await client.table('agent_versions').insert(initial_version_data).execute()
if version_result.data:
version_id = version_result.data[0]['version_id']
await client.table('agents').update({
'current_version_id': version_id,
'version_count': 1
}).eq('agent_id', agent_id).execute()
current_version_data = initial_version_data
logger.info(f"Created initial version for agent {agent_id}")
else:
current_version_data = {
'system_prompt': existing_data.get('system_prompt', ''),
'configured_mcps': existing_data.get('configured_mcps', []),
'custom_mcps': existing_data.get('custom_mcps', []),
'agentpress_tools': existing_data.get('agentpress_tools', {})
}
except Exception as e:
logger.warning(f"Failed to create initial version for agent {agent_id}: {e}")
current_version_data = {
'system_prompt': existing_data.get('system_prompt', ''),
'configured_mcps': existing_data.get('configured_mcps', []),
'custom_mcps': existing_data.get('custom_mcps', []),
'agentpress_tools': existing_data.get('agentpress_tools', {})
}
# 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'):
def values_different(new_val, old_val):
if new_val is None:
return False
import json
try:
new_json = json.dumps(new_val, sort_keys=True) if new_val is not None else None
old_json = json.dumps(old_val, sort_keys=True) if old_val is not None else None
return new_json != old_json
except (TypeError, ValueError):
return new_val != old_val
if values_different(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', []):
if values_different(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', []):
if values_different(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', {}):
if values_different(agent_data.agentpress_tools, current_version_data.get('agentpress_tools', {})):
needs_new_version = True
version_changes['agentpress_tools'] = agent_data.agentpress_tools
@ -1716,49 +1767,69 @@ async def update_agent(
# 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:
try:
# 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
# Validate version data before creating
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
}
# Validate system prompt is not empty
if not new_version_data["system_prompt"] or new_version_data["system_prompt"].strip() == '':
raise HTTPException(status_code=400, detail="System prompt cannot be empty")
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 new agent version")
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()
try:
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()
except Exception as e:
logger.warning(f"Failed to create version history entry: {e}")
logger.info(f"Created new version v{next_version_number} for agent {agent_id}")
except HTTPException:
raise
except Exception as e:
logger.error(f"Error creating new version for agent {agent_id}: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to create new agent version: {str(e)}")
# Update the agent if there are changes
if update_data:
update_result = await client.table('agents').update(update_data).eq("agent_id", agent_id).eq("account_id", user_id).execute()
if not update_result.data:
raise HTTPException(status_code=500, detail="Failed to update agent")
try:
update_result = await client.table('agents').update(update_data).eq("agent_id", agent_id).eq("account_id", user_id).execute()
if not update_result.data:
raise HTTPException(status_code=500, detail="Failed to update agent - no rows affected")
except Exception as e:
logger.error(f"Error updating agent {agent_id}: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to update agent: {str(e)}")
# Fetch the updated agent data with version info
updated_agent = await client.table('agents').select('*, agent_versions!current_version_id(*)').eq("agent_id", agent_id).eq("account_id", user_id).maybe_single().execute()

View File

@ -338,26 +338,37 @@ class TemplateManager:
if profile_mappings and req.qualified_name in profile_mappings:
profile_id = profile_mappings[req.qualified_name]
# Validate profile_id is not empty
if not profile_id or profile_id.strip() == '':
logger.error(f"Empty profile_id provided for {req.qualified_name}")
raise ValueError(f"Invalid credential profile selected for {req.display_name}")
# Get the credential profile
profile = await credential_manager.get_credential_by_profile(
account_id, profile_id
)
if not profile:
logger.warning(f"Credential profile not found for {req.qualified_name}")
continue
logger.error(f"Credential profile {profile_id} not found for {req.qualified_name}")
raise ValueError(f"Credential profile not found for {req.display_name}. Please select a valid profile or create a new one.")
# Validate profile is active
if not profile.is_active:
logger.error(f"Credential profile {profile_id} is inactive for {req.qualified_name}")
raise ValueError(f"Selected credential profile for {req.display_name} is inactive. Please select an active profile.")
mcp_config = {
'name': req.display_name,
'qualifiedName': req.qualified_name,
'config': profile.config,
'enabledTools': req.enabled_tools
'enabledTools': req.enabled_tools,
'selectedProfileId': profile_id
}
configured_mcps.append(mcp_config)
logger.info(f"Added regular MCP with profile: {mcp_config}")
else:
logger.warning(f"No profile mapping provided for {req.qualified_name}")
continue
logger.error(f"No profile mapping provided for {req.qualified_name}")
raise ValueError(f"Missing credential profile for {req.display_name}. Please select a credential profile.")
logger.info(f"Final configured_mcps: {configured_mcps}")
logger.info(f"Final custom_mcps: {custom_mcps}")
@ -381,8 +392,42 @@ class TemplateManager:
raise ValueError("Failed to create agent")
instance_id = result.data[0]['agent_id']
# Update template download count
try:
initial_version_data = {
"agent_id": instance_id,
"version_number": 1,
"version_name": "v1",
"system_prompt": agent_data['system_prompt'],
"configured_mcps": agent_data['configured_mcps'],
"custom_mcps": agent_data['custom_mcps'],
"agentpress_tools": agent_data['agentpress_tools'],
"is_active": True,
"created_by": account_id
}
version_result = await client.table('agent_versions').insert(initial_version_data).execute()
if version_result.data:
version_id = version_result.data[0]['version_id']
await client.table('agents').update({
'current_version_id': version_id,
'version_count': 1
}).eq('agent_id', instance_id).execute()
await client.table('agent_version_history').insert({
"agent_id": instance_id,
"version_id": version_id,
"action": "created",
"changed_by": account_id,
"change_description": "Initial version created from template installation"
}).execute()
logger.info(f"Created initial version v1 for installed agent {instance_id}")
else:
logger.warning(f"Failed to create initial version for agent {instance_id}")
except Exception as e:
logger.warning(f"Failed to create initial version for agent {instance_id}: {e}")
await client.table('agent_templates')\
.update({'download_count': template.download_count + 1})\
.eq('template_id', template_id).execute()

View File

@ -21,8 +21,9 @@ const MCPConfigurationItem: React.FC<{
const { data: profiles = [] } = useCredentialProfilesForMcp(mcp.qualifiedName);
const selectedProfile = profiles.find(p => p.profile_id === mcp.selectedProfileId);
const hasDirectConfig = mcp.config && Object.keys(mcp.config).length > 0;
const hasCredentialProfile = !!mcp.selectedProfileId && !!selectedProfile;
const needsConfiguration = !hasCredentialProfile;
const needsConfiguration = !hasCredentialProfile && !hasDirectConfig && !mcp.isCustom;
return (
<Card className="p-3">
@ -50,7 +51,13 @@ const MCPConfigurationItem: React.FC<{
</span>
</div>
)}
{needsConfiguration && !mcp.isCustom && (
{hasDirectConfig && !hasCredentialProfile && (
<div className="flex items-center gap-1">
<Key className="h-3 w-3 text-green-600" />
<span className="text-green-600 font-medium">Configured</span>
</div>
)}
{needsConfiguration && (
<div className="flex items-center gap-1">
<AlertTriangle className="h-3 w-3 text-amber-600" />
<span className="text-amber-600">Needs config</span>

View File

@ -156,18 +156,38 @@ export const UpdateAgentDialog = ({ agentId, isOpen, onOpenChange, onAgentUpdate
return;
}
if (!agentId) return;
if (!agentId) {
toast.error('Invalid agent ID');
return;
}
try {
await updateAgentMutation.mutateAsync({
agentId,
...formData
});
toast.success('Agent updated successfully!');
onOpenChange(false);
onAgentUpdated?.();
} catch (error) {
} catch (error: any) {
console.error('Error updating agent:', error);
// Error handling is managed by the mutation hook
if (error.message?.includes('System prompt cannot be empty')) {
toast.error('System prompt cannot be empty');
} else if (error.message?.includes('Failed to create new agent version')) {
toast.error('Failed to create new version. Please try again.');
} else if (error.message?.includes('Failed to update agent')) {
toast.error('Failed to update agent. Please check your configuration and try again.');
} else if (error.message?.includes('Agent not found')) {
toast.error('Agent not found. It may have been deleted.');
onOpenChange(false);
} else if (error.message?.includes('Access denied')) {
toast.error('You do not have permission to update this agent.');
onOpenChange(false);
} else {
toast.error(error.message || 'Failed to update agent. Please try again.');
}
}
};

View File

@ -714,6 +714,34 @@ export default function MarketplacePage() {
setInstallingItemId(item.id);
try {
if (!instanceName || instanceName.trim() === '') {
toast.error('Please provide a name for the agent');
return;
}
const regularRequirements = item.mcp_requirements?.filter(req => !req.custom_type) || [];
const missingProfiles = regularRequirements.filter(req =>
!profileMappings || !profileMappings[req.qualified_name] || profileMappings[req.qualified_name].trim() === ''
);
if (missingProfiles.length > 0) {
const missingNames = missingProfiles.map(req => req.display_name).join(', ');
toast.error(`Please select credential profiles for: ${missingNames}`);
return;
}
const customRequirements = item.mcp_requirements?.filter(req => req.custom_type) || [];
const missingCustomConfigs = customRequirements.filter(req =>
!customMcpConfigs || !customMcpConfigs[req.qualified_name] ||
req.required_config.some(field => !customMcpConfigs[req.qualified_name][field]?.trim())
);
if (missingCustomConfigs.length > 0) {
const missingNames = missingCustomConfigs.map(req => req.display_name).join(', ');
toast.error(`Please provide all required configuration for: ${missingNames}`);
return;
}
const result = await installTemplateMutation.mutateAsync({
template_id: item.template_id,
instance_name: instanceName,
@ -722,17 +750,35 @@ export default function MarketplacePage() {
});
if (result.status === 'installed') {
toast.success('Agent installed successfully!');
toast.success(`Agent "${instanceName}" installed successfully!`);
setShowInstallDialog(false);
} else if (result.status === 'configs_required') {
toast.error('Please provide all required configurations');
return;
} else {
toast.error('Unexpected response from server. Please try again.');
return;
}
} catch (error: any) {
console.error('Installation error:', error);
// Handle specific error types
if (error.message?.includes('already in your library')) {
toast.error('This agent is already in your library');
} else if (error.message?.includes('Credential profile not found')) {
toast.error('One or more selected credential profiles could not be found. Please refresh and try again.');
} else if (error.message?.includes('Missing credential profile')) {
toast.error('Please select credential profiles for all required services');
} else if (error.message?.includes('Invalid credential profile')) {
toast.error('One or more selected credential profiles are invalid. Please select valid profiles.');
} else if (error.message?.includes('inactive')) {
toast.error('One or more selected credential profiles are inactive. Please select active profiles.');
} else if (error.message?.includes('Template not found')) {
toast.error('This agent template is no longer available');
} else if (error.message?.includes('Access denied')) {
toast.error('You do not have permission to install this agent');
} else {
toast.error(error.message || 'Failed to install agent');
toast.error(error.message || 'Failed to install agent. Please try again.');
}
} finally {
setInstallingItemId(null);

View File

@ -411,7 +411,7 @@ export default function CredentialsPage() {
</p>
</div>
<Button onClick={() => setShowAddDialog(true)} className="h-9">
<Plus className="h-4 w-4 mr-2" />
<Plus className="h-4 w-4" />
Create First Profile
</Button>
</div>

View File

@ -352,8 +352,17 @@ export function CredentialProfileSelector({
<Select
value={selectedProfileId || ''}
onValueChange={(value) => {
const profile = profiles.find(p => p.profile_id === value);
onProfileSelect(value || null, profile || null);
if (value && value.trim() !== '') {
const profile = profiles.find(p => p.profile_id === value);
if (profile) {
onProfileSelect(value, profile);
} else {
console.error('Selected profile not found:', value);
toast.error('Selected profile not found. Please refresh and try again.');
}
} else {
onProfileSelect(null, null);
}
}}
disabled={disabled}
>