chain-studio/lib/pages/flows.dart
flemming-it 0cc527b07c
Some checks failed
Security / Security check (push) Failing after 1s
feat(studio): wire flow editor quick-fix install handler
FlowsPage now passes onInstallCapability to the editor — the
editor's 'Install <cap>' button on an unknown-capability
diagnostic calls HubService.installModule, refreshes the
capability list, and returns it so the editor reanalyzes and
clears the issue.

On install failure: friendly SnackBar surfaces the error. The
operator never gets stuck on the analyzer reporting a
capability they just clicked to install.

Studio bumped to 0.64.0; editor path-override pulls in 0.17.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-09 00:02:28 +02:00

118 lines
4 KiB
Dart

// FlowsPage — Studio's flow surface. Thin host wrapper around
// the swappable fai_studio_flow_editor package: loads the
// hub's installed capabilities, hands them + a Studio-side
// FlowRunDriver to the editor, and lets the editor own
// everything else (file list, three-tab body, properties
// panel, run progress, …).
//
// Pre-0.52 versions split file-listing and run-dialog state
// across this page. v0.52 moves all of that into the editor
// package so a swap is one pubspec change — Studio doesn't
// need to know what's inside the editor any more.
import 'package:fai_studio_flow_editor/fai_studio_flow_editor.dart';
import 'package:flutter/material.dart';
import '../data/flow_run_driver.dart';
import '../data/hub.dart';
class FlowsPage extends StatefulWidget {
/// Pre-load this flow when the editor first builds. Studio
/// keeps the parameter so the Cmd+K palette + welcome page
/// can deep-link straight into a specific flow.
final String? initialFlowName;
const FlowsPage({super.key, this.initialFlowName});
@override
State<FlowsPage> createState() => _FlowsPageState();
}
class _FlowsPageState extends State<FlowsPage> {
late Future<List<String>> _capabilities;
late final StudioFlowRunDriver _driver;
@override
void initState() {
super.initState();
_driver = StudioFlowRunDriver();
_capabilities = _loadCapabilities();
}
/// Pulls the capability identifiers the picker dialog
/// offers when adding a step. `<capability>@<version>` so
/// the operator can see exactly what they'll be wiring in,
/// matching the format the `use:` field expects. Soft-fails
/// to an empty list if the hub is unreachable — the editor
/// falls back to a free-form text field in that case.
Future<List<String>> _loadCapabilities() async {
try {
final caps = await HubService.instance.allCapabilities();
final entries =
caps.map((c) => '${c.capability}@${c.version}').toSet().toList()
..sort();
return entries;
} catch (_) {
return const <String>[];
}
}
/// Quick-fix handler — invoked when the operator clicks
/// `Install <capability>` on an analyzer issue. Parses the
/// editor-supplied `provider/name@version` spec into the
/// Hub's install API and returns the refreshed capability
/// list so the editor can re-analyze without waiting for the
/// parent widget rebuild.
Future<List<String>?> _onInstallCapability(String capability) async {
try {
// Editor passes the raw `use:` value (e.g.
// `text.classify@^1`). HubService.installModule resolves
// any version spec the hub's registry accepts.
await HubService.instance.installModule(source: capability);
final caps = await HubService.instance.allCapabilities();
final updated = caps
.map((c) => '${c.capability}@${c.version}')
.toSet()
.toList()
..sort();
if (mounted) {
// Refresh the cached capability future so subsequent
// FutureBuilder rebuilds also see the new list.
setState(() => _capabilities = Future.value(updated));
}
return updated;
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).removeCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Install failed: $e'),
behavior: SnackBarBehavior.floating,
),
);
}
return null;
}
}
@override
Widget build(BuildContext context) {
final locale = Localizations.localeOf(context);
final editorLocale = locale.languageCode == 'de'
? FlowEditorLocale.de
: FlowEditorLocale.en;
return FutureBuilder<List<String>>(
future: _capabilities,
builder: (context, snap) {
final caps = snap.data ?? const <String>[];
return FlowEditorPage(
initialFlowName: widget.initialFlowName,
locale: editorLocale,
runDriver: _driver,
availableCapabilities: caps,
onInstallCapability: _onInstallCapability,
);
},
);
}
}