Files
zaloopipe/frontend/lib/screens/pipeline_detail.dart
T

180 lines
5.3 KiB
Dart

import 'package:flutter/material.dart';
import '../api/client.dart';
import '../api/models.dart';
import 'run_screen.dart';
import 'pipeline_editor.dart';
class PipelineDetailScreen extends StatefulWidget {
final Pipeline pipeline;
const PipelineDetailScreen({super.key, required this.pipeline});
@override
State<PipelineDetailScreen> createState() => _PipelineDetailScreenState();
}
class _PipelineDetailScreenState extends State<PipelineDetailScreen> {
late APIClient _api;
late Pipeline _pipeline;
bool _loading = true;
@override
void initState() {
super.initState();
_pipeline = widget.pipeline;
_api = APIClient(baseUrl: _parseBaseUrl());
_load();
}
static String _parseBaseUrl() {
final uri = Uri.base;
return '${uri.scheme}://${uri.host}${uri.port != 80 && uri.port != 443 ? ':${uri.port}' : ''}';
}
Future<void> _load() async {
try {
final p = await _api.getPipeline(_pipeline.id);
setState(() {
_pipeline = p;
_loading = false;
});
} catch (e) {
setState(() => _loading = false);
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(e.toString())));
}
}
}
Future<void> _runPipeline() async {
await Navigator.push<void>(
context,
MaterialPageRoute(
builder: (_) => RunScreen(pipeline: _pipeline, api: _api),
),
);
_load();
}
Future<void> _editPipeline() async {
final result = await Navigator.push<Pipeline>(
context,
MaterialPageRoute(
builder: (_) => PipelineEditorScreen(pipeline: _pipeline),
),
);
if (result != null) {
setState(() => _pipeline = result);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_pipeline.name),
actions: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: _editPipeline,
tooltip: 'Edit',
),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _load,
tooltip: 'Refresh',
),
],
),
body: _loading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: double.infinity,
height: 48,
child: FilledButton.icon(
onPressed: _runPipeline,
icon: const Icon(Icons.play_arrow),
label: const Text('Run Pipeline'),
),
),
const SizedBox(height: 24),
_infoRow('ID', _pipeline.id),
_infoRow(
'Working Dir',
_pipeline.workingDir.isNotEmpty
? _pipeline.workingDir
: '(not set)',
),
_infoRow('Created', _pipeline.createdAt),
_infoRow('Updated', _pipeline.updatedAt),
const SizedBox(height: 24),
const Text(
'Prompts',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
..._pipeline.prompts.asMap().entries.map((entry) {
final i = entry.key;
final p = entry.value;
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Chip(label: Text('Step ${i + 1}')),
const SizedBox(width: 8),
Text(
p.title,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 8),
Text(p.text),
],
),
),
);
}),
],
),
),
);
}
Widget _infoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 100,
child: Text(
label,
style: const TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
),
),
),
Expanded(child: Text(value)),
],
),
);
}
}