import 'package:flutter/material.dart'; import 'package:iconsax/iconsax.dart'; import '../api/api.dart'; /// Dialog for creating a new AI agent /// /// Provides a comprehensive form with all required fields for agent creation. /// Includes validation and integrates with the CQRS API. class CreateAgentDialog extends StatefulWidget { final Function(CreateAgentCommand) onCreateAgent; const CreateAgentDialog({ super.key, required this.onCreateAgent, }); @override State createState() => _CreateAgentDialogState(); } class _CreateAgentDialogState extends State { final GlobalKey _formKey = GlobalKey(); // Form fields final TextEditingController _nameController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); final TextEditingController _modelProviderController = TextEditingController(); final TextEditingController _modelNameController = TextEditingController(); final TextEditingController _endpointController = TextEditingController(); final TextEditingController _apiKeyController = TextEditingController(); final TextEditingController _systemPromptController = TextEditingController(); final TextEditingController _maxTokensController = TextEditingController(text: '4000'); AgentType _selectedType = AgentType.codeGenerator; ModelProviderType _selectedProviderType = ModelProviderType.localEndpoint; double _temperature = 0.7; bool _enableMemory = true; int _conversationWindowSize = 10; bool _isCreating = false; @override void dispose() { _nameController.dispose(); _descriptionController.dispose(); _modelProviderController.dispose(); _modelNameController.dispose(); _endpointController.dispose(); _apiKeyController.dispose(); _systemPromptController.dispose(); _maxTokensController.dispose(); super.dispose(); } void _handleCreate() { if (_formKey.currentState!.validate()) { setState(() => _isCreating = true); final CreateAgentCommand command = CreateAgentCommand( name: _nameController.text.trim(), description: _descriptionController.text.trim(), type: _selectedType, modelProvider: _modelProviderController.text.trim(), modelName: _modelNameController.text.trim(), providerType: _selectedProviderType, modelEndpoint: _endpointController.text.trim().isEmpty ? null : _endpointController.text.trim(), apiKey: _apiKeyController.text.trim().isEmpty ? null : _apiKeyController.text.trim(), temperature: _temperature, maxTokens: int.parse(_maxTokensController.text), systemPrompt: _systemPromptController.text.trim(), enableMemory: _enableMemory, conversationWindowSize: _conversationWindowSize, ); widget.onCreateAgent(command); Navigator.of(context).pop(); } } @override Widget build(BuildContext context) { final ColorScheme colorScheme = Theme.of(context).colorScheme; return Dialog( backgroundColor: colorScheme.surface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: Container( width: 700, constraints: const BoxConstraints(maxHeight: 800), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: colorScheme.primaryContainer, borderRadius: const BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), ), ), child: Row( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: colorScheme.primary, borderRadius: BorderRadius.circular(12), ), child: const Icon( Iconsax.cpu, color: Colors.white, size: 24, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Create New Agent', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: colorScheme.onPrimaryContainer, ), ), const SizedBox(height: 4), Text( 'Configure your AI agent settings', style: TextStyle( fontSize: 13, color: colorScheme.onPrimaryContainer.withValues(alpha: 0.7), ), ), ], ), ), IconButton( icon: Icon( Iconsax.close_circle, color: colorScheme.onPrimaryContainer, ), onPressed: () => Navigator.of(context).pop(), ), ], ), ), // Form Content Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Basic Information Section _buildSectionHeader('Basic Information', Iconsax.information), const SizedBox(height: 16), _buildTextField( controller: _nameController, label: 'Agent Name', hint: 'e.g., Code Generator', icon: Iconsax.edit, validator: (String? value) { if (value == null || value.trim().isEmpty) { return 'Name is required'; } return null; }, ), const SizedBox(height: 16), _buildTextField( controller: _descriptionController, label: 'Description', hint: 'Describe what this agent does', icon: Iconsax.document_text, maxLines: 3, validator: (String? value) { if (value == null || value.trim().isEmpty) { return 'Description is required'; } return null; }, ), const SizedBox(height: 16), _buildDropdown( label: 'Agent Type', value: _selectedType, items: AgentType.values, itemLabel: (AgentType type) => type.value, onChanged: (AgentType? value) { if (value != null) { setState(() => _selectedType = value); } }, ), const SizedBox(height: 32), // Model Configuration Section _buildSectionHeader('Model Configuration', Iconsax.cpu), const SizedBox(height: 16), _buildDropdown( label: 'Provider Type', value: _selectedProviderType, items: ModelProviderType.values, itemLabel: (ModelProviderType type) => type.value, onChanged: (ModelProviderType? value) { if (value != null) { setState(() => _selectedProviderType = value); } }, ), const SizedBox(height: 16), _buildTextField( controller: _modelProviderController, label: 'Model Provider', hint: 'e.g., ollama, openai, anthropic', icon: Iconsax.cloud, validator: (String? value) { if (value == null || value.trim().isEmpty) { return 'Model provider is required'; } return null; }, ), const SizedBox(height: 16), _buildTextField( controller: _modelNameController, label: 'Model Name', hint: 'e.g., phi, gpt-4o, claude-3.5-sonnet', icon: Iconsax.cpu, validator: (String? value) { if (value == null || value.trim().isEmpty) { return 'Model name is required'; } return null; }, ), const SizedBox(height: 16), if (_selectedProviderType == ModelProviderType.localEndpoint) _buildTextField( controller: _endpointController, label: 'Model Endpoint', hint: 'http://localhost:11434', icon: Iconsax.link, ), if (_selectedProviderType == ModelProviderType.cloudApi) _buildTextField( controller: _apiKeyController, label: 'API Key', hint: 'sk-...', icon: Iconsax.key, obscureText: true, ), const SizedBox(height: 32), // Generation Parameters Section _buildSectionHeader('Generation Parameters', Iconsax.setting_2), const SizedBox(height: 16), _buildSlider( label: 'Temperature', value: _temperature, min: 0.0, max: 2.0, divisions: 20, onChanged: (double value) { setState(() => _temperature = value); }, ), const SizedBox(height: 16), _buildTextField( controller: _maxTokensController, label: 'Max Tokens', hint: '4000', icon: Iconsax.maximize, keyboardType: TextInputType.number, validator: (String? value) { if (value == null || value.trim().isEmpty) { return 'Max tokens is required'; } final int? tokens = int.tryParse(value); if (tokens == null || tokens <= 0) { return 'Must be a positive number'; } return null; }, ), const SizedBox(height: 16), _buildTextField( controller: _systemPromptController, label: 'System Prompt', hint: 'You are a helpful AI assistant...', icon: Iconsax.message_text, maxLines: 4, validator: (String? value) { if (value == null || value.trim().isEmpty) { return 'System prompt is required'; } return null; }, ), const SizedBox(height: 32), // Memory Settings Section _buildSectionHeader('Memory Settings', Iconsax.archive), const SizedBox(height: 16), _buildSwitch( label: 'Enable Memory', value: _enableMemory, onChanged: (bool value) { setState(() => _enableMemory = value); }, ), if (_enableMemory) ...[ const SizedBox(height: 16), _buildSlider( label: 'Conversation Window Size', value: _conversationWindowSize.toDouble(), min: 1, max: 100, divisions: 99, onChanged: (double value) { setState(() => _conversationWindowSize = value.toInt()); }, displayValue: _conversationWindowSize.toString(), ), ], ], ), ), ), ), // Footer Actions Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( border: Border( top: BorderSide( color: colorScheme.outline.withValues(alpha: 0.2), width: 1, ), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: _isCreating ? null : () => Navigator.of(context).pop(), child: const Text('Cancel'), ), const SizedBox(width: 12), ElevatedButton.icon( onPressed: _isCreating ? null : _handleCreate, icon: _isCreating ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : const Icon(Iconsax.add), label: Text(_isCreating ? 'Creating...' : 'Create Agent'), style: ElevatedButton.styleFrom( backgroundColor: colorScheme.primary, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 16, ), ), ), ], ), ), ], ), ), ); } Widget _buildSectionHeader(String title, IconData icon) { final ColorScheme colorScheme = Theme.of(context).colorScheme; return Row( children: [ Icon(icon, size: 20, color: colorScheme.primary), const SizedBox(width: 8), Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), ), ], ); } Widget _buildTextField({ required TextEditingController controller, required String label, required String hint, required IconData icon, String? Function(String?)? validator, int maxLines = 1, bool obscureText = false, TextInputType? keyboardType, }) { final ColorScheme colorScheme = Theme.of(context).colorScheme; return TextFormField( controller: controller, validator: validator, maxLines: maxLines, obscureText: obscureText, keyboardType: keyboardType, decoration: InputDecoration( labelText: label, hintText: hint, prefixIcon: Icon(icon, color: colorScheme.primary), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colorScheme.outline.withValues(alpha: 0.3), ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colorScheme.primary, width: 2, ), ), filled: true, fillColor: colorScheme.surfaceContainerLow, ), ); } Widget _buildDropdown({ required String label, required T value, required List items, required String Function(T) itemLabel, required void Function(T?) onChanged, }) { final ColorScheme colorScheme = Theme.of(context).colorScheme; return DropdownButtonFormField( initialValue: value, decoration: InputDecoration( labelText: label, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colorScheme.outline.withValues(alpha: 0.3), ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colorScheme.primary, width: 2, ), ), filled: true, fillColor: colorScheme.surfaceContainerLow, ), items: items.map((T item) { return DropdownMenuItem( value: item, child: Text(itemLabel(item)), ); }).toList(), onChanged: onChanged, ); } Widget _buildSlider({ required String label, required double value, required double min, required double max, required int divisions, required void Function(double) onChanged, String? displayValue, }) { final ColorScheme colorScheme = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: colorScheme.onSurface, ), ), Text( displayValue ?? value.toStringAsFixed(2), style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: colorScheme.primary, ), ), ], ), Slider( value: value, min: min, max: max, divisions: divisions, label: displayValue ?? value.toStringAsFixed(2), onChanged: onChanged, ), ], ); } Widget _buildSwitch({ required String label, required bool value, required void Function(bool) onChanged, }) { final ColorScheme colorScheme = Theme.of(context).colorScheme; return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: colorScheme.onSurface, ), ), Switch( value: value, onChanged: onChanged, activeTrackColor: colorScheme.primary, ), ], ); } }