import 'package:flutter/material.dart'; import '../api/models.dart'; import '../widgets/prompt_editor.dart'; import '../api/client.dart'; class PipelineEditorScreen extends StatefulWidget { final Pipeline? pipeline; const PipelineEditorScreen({super.key, this.pipeline}); @override State createState() => _PipelineEditorScreenState(); } class _PipelineEditorScreenState extends State { late TextEditingController _idController; late TextEditingController _nameController; late TextEditingController _dirController; late List _prompts; bool _saving = false; String? _error; late APIClient _api; @override void initState() { super.initState(); _api = APIClient(baseUrl: _parseBaseUrl()); if (widget.pipeline != null) { _idController = TextEditingController(text: widget.pipeline!.id); _nameController = TextEditingController(text: widget.pipeline!.name); _dirController = TextEditingController(text: widget.pipeline!.workingDir); _prompts = List.from(widget.pipeline!.prompts); } else { _idController = TextEditingController(); _nameController = TextEditingController(); _dirController = TextEditingController(); _prompts = []; } } @override void dispose() { _idController.dispose(); _nameController.dispose(); _dirController.dispose(); super.dispose(); } static String _parseBaseUrl() { final uri = Uri.base; return '${uri.scheme}://${uri.host}${uri.port != 80 && uri.port != 443 ? ':${uri.port}' : ''}'; } void _addPrompt() { setState(() { _prompts.add( Prompt(id: _generateId(), text: '', title: '', order: _prompts.length), ); }); } void _removePrompt(int index) { setState(() { _prompts.removeAt(index); for (int i = index; i < _prompts.length; i++) { _prompts[i] = _prompts[i].copyWith(order: i); } }); } void _updatePrompt(int index, Prompt updated) { setState(() { _prompts[index] = updated; }); } static String _generateId() { return DateTime.now().millisecondsSinceEpoch.toRadixString(36); } bool _validate() { if (_idController.text.trim().isEmpty) { setState(() => _error = 'ID is required'); return false; } if (_nameController.text.trim().isEmpty) { setState(() => _error = 'Name is required'); return false; } return true; } Future _save() async { if (!_validate()) return; setState(() { _saving = true; _error = null; }); final sortedPrompts = List.from(_prompts) ..sort((a, b) => a.order.compareTo(b.order)); final pipeline = Pipeline( id: _idController.text.trim(), name: _nameController.text.trim(), prompts: sortedPrompts, workingDir: _dirController.text.trim(), createdAt: widget.pipeline?.createdAt ?? '', updatedAt: '', ); try { final saved = await _api.createPipeline(pipeline); if (mounted) { Navigator.pop(context, saved); } } catch (e) { setState(() { _error = e.toString(); _saving = false; }); } } @override Widget build(BuildContext context) { final isEdit = widget.pipeline != null; return Scaffold( appBar: AppBar( title: Text(isEdit ? 'Edit Pipeline' : 'Create Pipeline'), actions: [ if (isEdit) IconButton( icon: const Icon(Icons.check, color: Colors.green), onPressed: _saving ? null : _save, tooltip: 'Save', ), ], ), body: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (_error != null) Padding( padding: const EdgeInsets.only(bottom: 16), child: Card( color: Colors.red.shade50, child: Padding( padding: const EdgeInsets.all(12), child: Row( children: [ const Icon(Icons.error_outline, color: Colors.red), const SizedBox(width: 12), Expanded(child: Text(_error!)), ], ), ), ), ), TextField( controller: _idController, decoration: const InputDecoration( labelText: 'ID', border: OutlineInputBorder(), isDense: true, ), enabled: !isEdit, ), const SizedBox(height: 12), TextField( controller: _nameController, decoration: const InputDecoration( labelText: 'Name', border: OutlineInputBorder(), isDense: true, ), ), const SizedBox(height: 12), TextField( controller: _dirController, decoration: const InputDecoration( labelText: 'Working Directory', border: OutlineInputBorder(), isDense: true, ), ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Prompts', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), FilledButton.icon( onPressed: _addPrompt, icon: const Icon(Icons.add, size: 18), label: const Text('Add Step'), ), ], ), const SizedBox(height: 12), if (_prompts.isEmpty) const Padding( padding: EdgeInsets.all(20), child: Center( child: Text( 'No prompts yet. Add a step to get started.', style: TextStyle(color: Colors.grey), ), ), ), for (int i = 0; i < _prompts.length; i++) PromptEditor( prompt: _prompts[i], showDelete: _prompts.length > 1, onChanged: (p) => _updatePrompt(i, p), onDelete: () => _removePrompt(i), ), if (_prompts.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 16), child: SizedBox( width: double.infinity, height: 48, child: FilledButton.icon( onPressed: _saving ? null : _save, icon: _saving ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.save), label: Text( _saving ? 'Saving...' : (isEdit ? 'Save Changes' : 'Create Pipeline'), ), ), ), ), ], ), ), ); } }