Implement full-featured agent creation dialog with extensive form fields,
validation, and Material 3 design. Fully integrated with CQRS API backend.
## What's New
- **Create Agent Dialog**: 575 lines of production-ready UI
- **Complete Form**: All 13 agent configuration fields
- **Validation**: Field-level validation with user-friendly messages
- **Dynamic UI**: Form adapts based on provider type selection
- **Testing Guide**: Comprehensive manual testing documentation
## Dialog Features
### Form Sections
1. **Basic Information**
- Agent name (required)
- Description (required)
- Agent type dropdown (5 types)
2. **Model Configuration**
- Provider type (CloudApi/LocalEndpoint/Custom)
- Model provider (e.g., ollama, openai)
- Model name (e.g., phi, gpt-4o)
- Conditional fields:
- Endpoint input (for local models)
- API key input (for cloud providers, obscured)
3. **Generation Parameters**
- Temperature slider (0.0-2.0)
- Max tokens input (validated)
- System prompt textarea (4 lines)
4. **Memory Settings**
- Enable memory toggle
- Conversation window size slider (1-100)
### UI/UX Enhancements
✅ Material 3 design language
✅ Svrnty brand colors throughout
✅ Icon-prefixed input fields
✅ Smooth animations and transitions
✅ Responsive layout (700px width, scrollable)
✅ Loading state on submit
✅ Form validation with error messages
✅ Cancel and Create buttons
✅ Professional header with agent icon
### Integration
✅ Connected to AgentsPage
✅ Calls _createAgent() with CreateAgentCommand
✅ API client integration ready
✅ SnackBar notifications for feedback
✅ Dialog closes on success
## Technical Details
- **Lines**: 575 (dialog) + updates
- **Widgets**: Custom form components
- _buildTextField() with validation
- _buildDropdown() with generics
- _buildSlider() with live values
- _buildSwitch() with styling
- _buildSectionHeader() with icons
- **State Management**: StatefulWidget with form state
- **Validation**: GlobalKey<FormState> pattern
- **Type Safety**: 100% explicit types
- **Dispose**: Proper controller cleanup
## Files Added
- lib/dialogs/create_agent_dialog.dart (575 lines)
- docs/TESTING_GUIDE.md (450 lines)
## Files Modified
- lib/pages/agents_page.dart (+3 lines - dialog integration)
## Testing
- Flutter analyze: 0 errors, 0 warnings ✅
- Hot reload compatible ✅
- Form validation tested ✅
- All field types working ✅
## User Flow
1. Click "Create Agent" button (anywhere)
2. Dialog opens with smooth animation
3. Fill required fields (validated)
4. Choose provider type (form adapts)
5. Adjust sliders for parameters
6. Review all settings
7. Click "Create Agent"
8. Loading indicator shows
9. API call executes
10. Success message displays
11. Dialog closes
12. Agent appears in list (when backend ready)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
488 lines
14 KiB
Dart
488 lines
14 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:iconsax/iconsax.dart';
|
|
import 'package:animate_do/animate_do.dart';
|
|
import 'package:getwidget/getwidget.dart';
|
|
import '../api/api.dart';
|
|
import '../dialogs/create_agent_dialog.dart';
|
|
|
|
/// Agents management page
|
|
///
|
|
/// Displays all AI agents with ability to create, view, edit, and delete agents.
|
|
/// Integrates with backend CQRS API for agent management.
|
|
class AgentsPage extends StatefulWidget {
|
|
const AgentsPage({super.key});
|
|
|
|
@override
|
|
State<AgentsPage> createState() => _AgentsPageState();
|
|
}
|
|
|
|
class _AgentsPageState extends State<AgentsPage> {
|
|
final CqrsApiClient _apiClient = CqrsApiClient(
|
|
config: ApiClientConfig.development,
|
|
);
|
|
|
|
List<AgentDto>? _agents;
|
|
bool _isLoading = true;
|
|
String? _errorMessage;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadAgents();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_apiClient.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _loadAgents() async {
|
|
setState(() {
|
|
_isLoading = true;
|
|
_errorMessage = null;
|
|
});
|
|
|
|
// TODO: Implement list agents endpoint when available
|
|
// For now, show empty state
|
|
await Future<void>.delayed(const Duration(milliseconds: 500));
|
|
|
|
setState(() {
|
|
_agents = [];
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
|
|
Future<void> _createAgent(CreateAgentCommand command) async {
|
|
final Result<void> result = await _apiClient.createAgent(command);
|
|
|
|
result.when(
|
|
success: (_) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Agent "${command.name}" created successfully'),
|
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
|
),
|
|
);
|
|
_loadAgents();
|
|
}
|
|
},
|
|
error: (ApiErrorInfo error) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Failed to create agent: ${error.message}'),
|
|
backgroundColor: Theme.of(context).colorScheme.error,
|
|
),
|
|
);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header Section
|
|
_buildHeader(colorScheme),
|
|
const SizedBox(height: 24),
|
|
|
|
// Content Section
|
|
Expanded(
|
|
child: _buildContent(colorScheme),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHeader(ColorScheme colorScheme) {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
// Title & Description
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'AI Agents',
|
|
style: TextStyle(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.bold,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'Manage your AI agents and their configurations',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
// Create Agent Button
|
|
FadeInRight(
|
|
duration: const Duration(milliseconds: 400),
|
|
child: ElevatedButton.icon(
|
|
onPressed: () => _showCreateAgentDialog(),
|
|
icon: const Icon(Iconsax.add, size: 20),
|
|
label: const Text('Create Agent'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: colorScheme.primary,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildContent(ColorScheme colorScheme) {
|
|
if (_isLoading) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
CircularProgressIndicator(
|
|
valueColor: AlwaysStoppedAnimation<Color>(colorScheme.primary),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Loading agents...',
|
|
style: TextStyle(
|
|
color: colorScheme.onSurfaceVariant,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
if (_errorMessage != null) {
|
|
return _buildErrorState(colorScheme);
|
|
}
|
|
|
|
if (_agents == null || _agents!.isEmpty) {
|
|
return _buildEmptyState(colorScheme);
|
|
}
|
|
|
|
return _buildAgentsList(colorScheme);
|
|
}
|
|
|
|
Widget _buildEmptyState(ColorScheme colorScheme) {
|
|
return Center(
|
|
child: FadeIn(
|
|
duration: const Duration(milliseconds: 600),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Iconsax.cpu,
|
|
size: 80,
|
|
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Text(
|
|
'No Agents Yet',
|
|
style: TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'Create your first AI agent to get started',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 32),
|
|
ElevatedButton.icon(
|
|
onPressed: () => _showCreateAgentDialog(),
|
|
icon: const Icon(Iconsax.add),
|
|
label: const Text('Create Your First Agent'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: colorScheme.primary,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 32,
|
|
vertical: 16,
|
|
),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildErrorState(ColorScheme colorScheme) {
|
|
return Center(
|
|
child: FadeIn(
|
|
duration: const Duration(milliseconds: 400),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Iconsax.danger,
|
|
size: 64,
|
|
color: colorScheme.error,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Error Loading Agents',
|
|
style: TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
_errorMessage ?? 'Unknown error',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 24),
|
|
ElevatedButton.icon(
|
|
onPressed: _loadAgents,
|
|
icon: const Icon(Iconsax.refresh),
|
|
label: const Text('Retry'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: colorScheme.primary,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildAgentsList(ColorScheme colorScheme) {
|
|
return GridView.builder(
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
maxCrossAxisExtent: 400,
|
|
childAspectRatio: 1.5,
|
|
crossAxisSpacing: 16,
|
|
mainAxisSpacing: 16,
|
|
),
|
|
itemCount: _agents!.length,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return FadeInUp(
|
|
duration: Duration(milliseconds: 300 + (index * 100)),
|
|
child: _buildAgentCard(_agents![index], colorScheme),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildAgentCard(AgentDto agent, ColorScheme colorScheme) {
|
|
return Card(
|
|
elevation: 0,
|
|
color: colorScheme.surfaceContainerLow,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
side: BorderSide(
|
|
color: colorScheme.outline.withValues(alpha: 0.2),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: InkWell(
|
|
onTap: () => _showAgentDetails(agent),
|
|
borderRadius: BorderRadius.circular(16),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header Row
|
|
Row(
|
|
children: [
|
|
// Agent Type Icon
|
|
Container(
|
|
padding: const EdgeInsets.all(10),
|
|
decoration: BoxDecoration(
|
|
color: colorScheme.primary.withValues(alpha: 0.2),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Icon(
|
|
_getAgentTypeIcon(agent.type),
|
|
color: colorScheme.primary,
|
|
size: 24,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
// Agent Name & Status
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
agent.name,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
const SizedBox(height: 4),
|
|
_buildStatusBadge(agent.status, colorScheme),
|
|
],
|
|
),
|
|
),
|
|
// More Options
|
|
IconButton(
|
|
icon: Icon(
|
|
Iconsax.more,
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
onPressed: () => _showAgentMenu(agent),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
// Description
|
|
Text(
|
|
agent.description,
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
const Spacer(),
|
|
// Footer
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Iconsax.cpu,
|
|
size: 14,
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
const SizedBox(width: 6),
|
|
Expanded(
|
|
child: Text(
|
|
'${agent.modelProvider}/${agent.modelName}',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatusBadge(AgentStatus status, ColorScheme colorScheme) {
|
|
Color badgeColor;
|
|
IconData icon;
|
|
|
|
switch (status) {
|
|
case AgentStatus.active:
|
|
badgeColor = Colors.green;
|
|
icon = Iconsax.tick_circle5;
|
|
case AgentStatus.inactive:
|
|
badgeColor = Colors.orange;
|
|
icon = Iconsax.pause_circle5;
|
|
case AgentStatus.error:
|
|
badgeColor = colorScheme.error;
|
|
icon = Iconsax.danger5;
|
|
}
|
|
|
|
return Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(icon, size: 12, color: badgeColor),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
status.value,
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
color: badgeColor,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
IconData _getAgentTypeIcon(AgentType type) {
|
|
switch (type) {
|
|
case AgentType.codeGenerator:
|
|
return Iconsax.code;
|
|
case AgentType.codeReviewer:
|
|
return Iconsax.search_zoom_in;
|
|
case AgentType.debugger:
|
|
return Iconsax.shield_search;
|
|
case AgentType.documenter:
|
|
return Iconsax.document_text;
|
|
case AgentType.custom:
|
|
return Iconsax.setting_2;
|
|
}
|
|
}
|
|
|
|
void _showCreateAgentDialog() {
|
|
showDialog<void>(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return CreateAgentDialog(
|
|
onCreateAgent: _createAgent,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
void _showAgentDetails(AgentDto agent) {
|
|
// TODO: Implement agent details view
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Viewing agent: ${agent.name}'),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showAgentMenu(AgentDto agent) {
|
|
// TODO: Implement agent menu (edit, delete, etc.)
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Agent menu for: ${agent.name}'),
|
|
),
|
|
);
|
|
}
|
|
}
|