255 lines
7.4 KiB
Dart
255 lines
7.4 KiB
Dart
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<PipelineEditorScreen> createState() => _PipelineEditorScreenState();
|
|
}
|
|
|
|
class _PipelineEditorScreenState extends State<PipelineEditorScreen> {
|
|
late TextEditingController _idController;
|
|
late TextEditingController _nameController;
|
|
late TextEditingController _dirController;
|
|
late List<Prompt> _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<void> _save() async {
|
|
if (!_validate()) return;
|
|
|
|
setState(() {
|
|
_saving = true;
|
|
_error = null;
|
|
});
|
|
|
|
final sortedPrompts = List<Prompt>.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'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|