Spaces:
Sleeping
Sleeping
| <template> | |
| <div class="modal-overlay" @click.self="$emit('close')"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <h2 class="modal-title">Add New Agent</h2> | |
| <button class="close-button" @click="$emit('close')"> | |
| ✕ | |
| </button> | |
| </div> | |
| <div class="modal-body"> | |
| <form @submit.prevent="handleSubmit"> | |
| <!-- Loading Indicator --> | |
| <div v-if="loading" class="loading-overlay"> | |
| <div class="loading-spinner"></div> | |
| <p class="loading-text">Creating agent...</p> | |
| </div> | |
| <!-- Error Message --> | |
| <div v-if="error" class="error-message"> | |
| <span class="error-icon">❌</span> | |
| <span>{{ error }}</span> | |
| </div> | |
| <!-- Success Message --> | |
| <div v-if="successMessage" class="success-message"> | |
| <span class="success-icon">✅</span> | |
| <span>{{ successMessage }}</span> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Agent Name *</label> | |
| <input | |
| v-model="form.name" | |
| type="text" | |
| class="form-input" | |
| placeholder="Enter agent name" | |
| required | |
| :disabled="loading" | |
| /> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Agent Type *</label> | |
| <select v-model="form.type" class="form-input" required :disabled="loading"> | |
| <option value="">Select type...</option> | |
| <option value="coordinator">Coordinator</option> | |
| <option value="developer">Developer</option> | |
| <option value="specialist">Specialist</option> | |
| <option value="generalist">Generalist</option> | |
| <option value="tool_user">Tool User</option> | |
| <option value="monitor">Monitor</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Description</label> | |
| <textarea | |
| v-model="form.description" | |
| class="form-input resize-y" | |
| rows="3" | |
| placeholder="Agent description and capabilities" | |
| :disabled="loading" | |
| ></textarea> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Capabilities (comma-separated)</label> | |
| <input | |
| v-model="form.capabilities" | |
| type="text" | |
| class="form-input" | |
| placeholder="e.g., coding, debugging, analysis" | |
| :disabled="loading" | |
| /> | |
| <small class="form-help"> | |
| Available: orchestration, coordination, strategy, coding, debugging, architecture, analysis, research, reporting, medical_advice, diagnosis, treatment, legal_advice, compliance, contracts, financial_analysis, investment, budgeting | |
| </small> | |
| </div> | |
| <div class="form-actions"> | |
| <button type="button" class="cancel-button" @click="$emit('close')" :disabled="loading"> | |
| Cancel | |
| </button> | |
| <button type="submit" class="submit-button" :disabled="!isFormValid || loading"> | |
| {{ loading ? 'Creating...' : 'Create Agent' }} | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| <script> | |
| import { ref, reactive, computed } from 'vue' | |
| import { saapApi } from '../../services/saapApi.js' | |
| export default { | |
| name: 'AddAgentModal', | |
| emits: ['close', 'agent-created'], | |
| setup(props, { emit }) { | |
| const loading = ref(false) | |
| const error = ref('') | |
| const successMessage = ref('') | |
| const form = reactive({ | |
| name: '', | |
| type: '', | |
| description: '', | |
| capabilities: '' | |
| }) | |
| const isFormValid = computed(() => { | |
| return form.name.trim() && form.type.trim() | |
| }) | |
| const handleSubmit = async () => { | |
| if (!isFormValid.value || loading.value) return | |
| loading.value = true | |
| error.value = '' | |
| successMessage.value = '' | |
| try { | |
| // 🚀 FIXED: Properly structured agent data matching backend schema | |
| const capabilities = validateAndCleanCapabilities(form.capabilities) | |
| const agentId = generateAgentId(form.name) | |
| const description = form.description.trim() || `${form.name} - ${form.type} agent` | |
| const agentData = { | |
| // Core Identity | |
| id: agentId, | |
| name: form.name.trim(), | |
| type: form.type, | |
| status: "inactive", | |
| description: description, | |
| // Metadata (properly nested) | |
| metadata: { | |
| version: "1.0.0", | |
| created: new Date().toISOString(), | |
| updated: new Date().toISOString(), | |
| creator: "user", | |
| tags: [form.type, "custom_agent"] | |
| }, | |
| // Appearance (properly nested) | |
| appearance: { | |
| color: getAgentColor(form.type), | |
| avatar: `/assets/agents/${agentId}.svg`, | |
| display_name: form.name.trim(), | |
| subtitle: `${form.type.charAt(0).toUpperCase() + form.type.slice(1)} Agent`, | |
| description: description | |
| }, | |
| // Capabilities | |
| capabilities: capabilities, | |
| // LLM Config (with all required fields) | |
| llm_config: { | |
| model: "phi3:mini", | |
| temperature: 0.7, | |
| max_tokens: 2048, | |
| top_p: 0.9, | |
| system_prompt: `You are ${form.name.trim()}, a ${form.type} agent with the following capabilities: ${capabilities.join(', ')}. ${description}`, | |
| stop_sequences: [], | |
| context_window: 4096 | |
| }, | |
| // Communication (required field) | |
| communication: { | |
| input_queue: `${agentId}_input`, | |
| output_queue: `${agentId}_output`, | |
| message_types: ["request", "response"], | |
| max_queue_size: 1000, | |
| message_ttl: 3600, | |
| priority_handling: true | |
| }, | |
| // UI Components | |
| ui_components: { | |
| dashboard_widget: "AgentCard", | |
| detail_view: "AgentDetail", | |
| configuration_form: "AgentConfig" | |
| }, | |
| // Custom config (extensibility) | |
| custom_config: {} | |
| } | |
| console.log('🚀 Creating agent with backend-compatible data:', agentData) | |
| // API call | |
| const response = await saapApi.createAgent(agentData) | |
| console.log('✅ Backend response:', response) | |
| // Handle response formats | |
| if (response.success === true && response.agent) { | |
| console.log('✅ Agent created successfully (format 1):', response.agent) | |
| successMessage.value = response.message || 'Agent created successfully!' | |
| resetForm() | |
| emit('agent-created', response.agent) | |
| setTimeout(() => emit('close'), 1500) | |
| } else if (response.id || response.agent_id) { | |
| console.log('✅ Agent created successfully (format 2):', response) | |
| successMessage.value = 'Agent created successfully!' | |
| resetForm() | |
| emit('agent-created', response) | |
| setTimeout(() => emit('close'), 1500) | |
| } else { | |
| console.warn('⚠️ Unexpected response format:', response) | |
| throw new Error('Unexpected response format from server') | |
| } | |
| } catch (err) { | |
| console.error('❌ Agent creation failed:', err) | |
| // Better error handling | |
| if (err.response) { | |
| const status = err.response.status | |
| const data = err.response.data | |
| if (status === 422) { | |
| error.value = `Validation error: ${data.detail || 'Invalid agent data'}` | |
| } else if (status === 409) { | |
| error.value = `Agent already exists: ${data.detail || 'Choose a different name'}` | |
| } else if (status === 500) { | |
| error.value = `Server error: ${data.detail || 'Please try again later'}` | |
| } else { | |
| error.value = data.detail || `HTTP ${status}: Request failed` | |
| } | |
| } else if (err.code === 'ECONNREFUSED' || err.message.includes('Network Error')) { | |
| error.value = 'Backend server is not running. Please start the SAAP backend.' | |
| } else if (err.message) { | |
| error.value = err.message | |
| } else { | |
| error.value = 'Failed to create agent. Please check your network and try again.' | |
| } | |
| } finally { | |
| loading.value = false | |
| } | |
| } | |
| const resetForm = () => { | |
| form.name = '' | |
| form.type = '' | |
| form.description = '' | |
| form.capabilities = '' | |
| } | |
| // Generate agent ID from name | |
| const generateAgentId = (name) => { | |
| return name | |
| .toLowerCase() | |
| .replace(/[^a-z0-9\s]/g, '') | |
| .replace(/\s+/g, '_') | |
| .substring(0, 20) | |
| } | |
| // Get agent color by type | |
| const getAgentColor = (type) => { | |
| const typeColors = { | |
| 'coordinator': '#8B5CF6', // Purple | |
| 'developer': '#14B8A6', // Teal | |
| 'specialist': '#F59E0B', // Gold | |
| 'generalist': '#EC4899', // Pink | |
| 'tool_user': '#10B981', // Green | |
| 'monitor': '#6366F1' // Blue | |
| } | |
| return typeColors[type] || '#6B7280' | |
| } | |
| // 🚀 FIX: Validate capabilities against backend allowed values | |
| const validateAndCleanCapabilities = (capabilitiesString) => { | |
| if (!capabilitiesString) return getDefaultCapabilities(form.type) | |
| const allowedCapabilities = new Set([ | |
| 'orchestration', 'coordination', 'strategy', | |
| 'coding', 'debugging', 'architecture', | |
| 'analysis', 'research', 'reporting', | |
| 'medical_advice', 'diagnosis', 'treatment', | |
| 'legal_advice', 'compliance', 'contracts', | |
| 'financial_analysis', 'investment', 'budgeting', | |
| 'system_integration', 'devops', 'monitoring', | |
| 'coaching', 'training', 'change_management' | |
| ]) | |
| const userCapabilities = capabilitiesString | |
| .split(',') | |
| .map(cap => cap.trim().toLowerCase().replace(/\s+/g, '_')) | |
| .filter(cap => cap && allowedCapabilities.has(cap)) | |
| return userCapabilities.length > 0 ? userCapabilities : getDefaultCapabilities(form.type) | |
| } | |
| // Default capabilities by type | |
| const getDefaultCapabilities = (type) => { | |
| const defaultCapabilities = { | |
| 'coordinator': ['orchestration', 'coordination', 'strategy'], | |
| 'developer': ['coding', 'debugging', 'architecture'], | |
| 'specialist': ['research', 'analysis', 'strategy'], | |
| 'generalist': ['analysis', 'research', 'reporting'], | |
| 'tool_user': ['system_integration', 'devops', 'monitoring'], | |
| 'monitor': ['monitoring', 'analysis', 'reporting'] | |
| } | |
| return defaultCapabilities[type] || ['analysis', 'research'] | |
| } | |
| return { | |
| form, | |
| loading, | |
| error, | |
| successMessage, | |
| isFormValid, | |
| handleSubmit | |
| } | |
| } | |
| } | |
| </script> | |
| <style scoped> | |
| .modal-overlay { | |
| @apply fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50; | |
| } | |
| .modal-content { | |
| @apply bg-white rounded-lg shadow-xl max-w-md w-full mx-4 relative; | |
| } | |
| .loading-overlay { | |
| @apply absolute inset-0 bg-white bg-opacity-90 flex flex-col items-center justify-center z-10 rounded-lg; | |
| } | |
| .loading-spinner { | |
| @apply w-8 h-8 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin; | |
| } | |
| .loading-text { | |
| @apply mt-4 text-sm font-medium text-gray-600; | |
| } | |
| .error-message { | |
| @apply bg-red-50 border border-red-200 rounded-md p-3 mb-4 flex items-center space-x-2 text-red-700; | |
| } | |
| .error-icon { | |
| @apply text-lg; | |
| } | |
| .success-message { | |
| @apply bg-green-50 border border-green-200 rounded-md p-3 mb-4 flex items-center space-x-2 text-green-700; | |
| } | |
| .success-icon { | |
| @apply text-lg; | |
| } | |
| .modal-header { | |
| @apply flex items-center justify-between p-6 border-b border-gray-200; | |
| } | |
| .modal-title { | |
| @apply text-lg font-semibold text-gray-900; | |
| } | |
| .close-button { | |
| @apply text-gray-400 hover:text-gray-600 text-xl font-bold w-8 h-8 flex items-center justify-center; | |
| } | |
| .modal-body { | |
| @apply p-6; | |
| } | |
| .form-group { | |
| @apply mb-4; | |
| } | |
| .form-label { | |
| @apply block text-sm font-medium text-gray-700 mb-2; | |
| } | |
| .form-input { | |
| @apply w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed; | |
| } | |
| .form-input:disabled { | |
| @apply opacity-50; | |
| } | |
| .form-help { | |
| @apply block text-xs text-gray-500 mt-1; | |
| } | |
| .form-actions { | |
| @apply flex space-x-3 mt-6; | |
| } | |
| .cancel-button { | |
| @apply flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50 font-medium disabled:opacity-50 disabled:cursor-not-allowed; | |
| } | |
| .submit-button { | |
| @apply flex-1 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 font-medium disabled:bg-gray-400 disabled:cursor-not-allowed; | |
| } | |
| </style> | |