Hwandji's picture
feat: initial HuggingFace Space deployment
4343907
raw
history blame
10.8 kB
<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>