feat(studio): Flows page IS the editor — three-tab graph+text+run
Some checks failed
Security / Security check (push) Failing after 1s
Some checks failed
Security / Security check (push) Failing after 1s
Studio's Flows destination is now the swappable fai_studio_flow_editor's full surface: a drag-and-drop graph view, the raw YAML text editor, and the run tab with inputs form + live step progress + outputs. What's gone: - The old saved-flows list + per-row run dialog. - The separate "flow-editor" navigation destination (one icon, not two — operators reach the editor by clicking Flows, period). - _FlowEditorAdapter (no longer needed; FlowsPage hands the runDriver to the editor directly). What's new: - lib/data/flow_run_driver.dart — StudioFlowRunDriver: adapter implementing the editor's FlowRunDriver. Maps runFlow() to HubService.runSavedFlow and events() to HubService.streamEvents filtered to step.* event types. Converts proto-shaped FlowOutput variants to the editor's host-agnostic FlowOutputValue family. - lib/pages/flows.dart — replaced with a 50-line wrapper that loads capabilities into the editor's picker dropdown and forwards the active locale. Editor pinned to fai_studio_flow_editor 0.2.0 (commit 870cbc2). All editor state — file list, dirty tracking, tab selection, graph layout sidecar, properties panel — lives in the editor package now; Studio just supplies the hub bridge. Version 0.51.10 -> 0.52.0 (minor bump — Flows-page contract changed, separate flow-editor destination removed). flutter analyze: 0 issues. flutter test: 12/12 green. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
fe2ee0a323
commit
634fd52635
5 changed files with 179 additions and 1439 deletions
112
lib/data/flow_run_driver.dart
Normal file
112
lib/data/flow_run_driver.dart
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// StudioFlowRunDriver — adapter that bridges the editor
|
||||
// package's FlowRunDriver interface to Studio's HubService.
|
||||
//
|
||||
// Lives in Studio (not in the editor package) so the package
|
||||
// stays host-agnostic. The driver maps:
|
||||
//
|
||||
// - editor.runFlow(...) -> HubService.runSavedFlow
|
||||
// - editor.events() -> HubService.streamEvents filtered
|
||||
// to step.* events and reshaped
|
||||
// into the editor's FlowRunEvent
|
||||
// value classes.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fai_studio_flow_editor/fai_studio_flow_editor.dart' as editor;
|
||||
|
||||
import 'hub.dart';
|
||||
|
||||
class StudioFlowRunDriver implements editor.FlowRunDriver {
|
||||
StudioFlowRunDriver();
|
||||
|
||||
@override
|
||||
Future<Map<String, editor.FlowOutputValue>> runFlow({
|
||||
required String flowName,
|
||||
required Map<String, String> textInputs,
|
||||
required Map<String, Uint8List> fileInputs,
|
||||
required Map<String, String> fileMimes,
|
||||
}) async {
|
||||
final outputs = await HubService.instance.runSavedFlow(
|
||||
name: flowName,
|
||||
textInputs: textInputs,
|
||||
fileInputs: fileInputs,
|
||||
fileMimeTypes: fileMimes,
|
||||
);
|
||||
final mapped = <String, editor.FlowOutputValue>{};
|
||||
for (final entry in outputs.entries) {
|
||||
mapped[entry.key] = _convertOutput(entry.value);
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<editor.FlowRunEvent> events() {
|
||||
return HubService.instance
|
||||
.streamEvents(
|
||||
backfill: 0,
|
||||
types: const [
|
||||
'step.started',
|
||||
'step.completed',
|
||||
'step.failed',
|
||||
'step.awaiting_approval',
|
||||
'step.approved',
|
||||
'step.rejected',
|
||||
],
|
||||
)
|
||||
.map(_convertEvent)
|
||||
.where((e) => e != null)
|
||||
.cast<editor.FlowRunEvent>();
|
||||
}
|
||||
|
||||
editor.FlowRunEvent? _convertEvent(AuditEvent e) {
|
||||
final flowName = e.flowName;
|
||||
final stepId = e.stepId;
|
||||
if (flowName == null || flowName.isEmpty) return null;
|
||||
if (stepId == null || stepId.isEmpty) return null;
|
||||
switch (e.type) {
|
||||
case 'step.started':
|
||||
case 'step.approved':
|
||||
return editor.StepStarted(flowName: flowName, stepId: stepId);
|
||||
case 'step.completed':
|
||||
return editor.StepCompleted(
|
||||
flowName: flowName,
|
||||
stepId: stepId,
|
||||
durationMs: e.durationMs ?? 0,
|
||||
);
|
||||
case 'step.failed':
|
||||
case 'step.rejected':
|
||||
return editor.StepFailed(
|
||||
flowName: flowName,
|
||||
stepId: stepId,
|
||||
error: e.error ?? '',
|
||||
);
|
||||
case 'step.awaiting_approval':
|
||||
return editor.StepAwaitingApproval(
|
||||
flowName: flowName,
|
||||
stepId: stepId,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Studio's FlowOutput hierarchy is gRPC-shaped (one class
|
||||
/// per payload variant); the editor's is host-agnostic
|
||||
/// (text / json / bytes only). File outputs degrade to
|
||||
/// JSON containing the URI string — the editor's run-tab
|
||||
/// renderer is read-only so the operator can still see
|
||||
/// where the file went; Studio's richer FaiFlowOutput
|
||||
/// widget keeps file-open affordances available elsewhere.
|
||||
editor.FlowOutputValue _convertOutput(FlowOutput out) {
|
||||
return switch (out) {
|
||||
FlowOutputText() => editor.FlowOutputText(out.text),
|
||||
FlowOutputJson() => editor.FlowOutputJson(out.pretty),
|
||||
FlowOutputBytes() => editor.FlowOutputBytes(out.bytes, out.mimeType),
|
||||
FlowOutputFile() => editor.FlowOutputJson({
|
||||
'file_uri': out.uri,
|
||||
'mime_type': out.mimeType,
|
||||
}),
|
||||
FlowOutputUnknown() => const editor.FlowOutputText(''),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fai_studio_flow_editor/fai_studio_flow_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
|
|
@ -27,7 +26,7 @@ import 'widgets/widgets.dart';
|
|||
/// Studio's own build version. Bump on every UI commit so the
|
||||
/// running app self-identifies — visible in the sidebar header
|
||||
/// and quick-glance proof that you're seeing the current build.
|
||||
const String kStudioVersion = '0.51.10';
|
||||
const String kStudioVersion = '0.52.0';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
|
@ -228,18 +227,17 @@ class _StudioShellState extends State<StudioShell> {
|
|||
// the Store's detail sheet, and the Store with the
|
||||
// "Installed" filter covers the listing role. Cmd+K still
|
||||
// opens FaiModuleSheet for quick info.
|
||||
// Flows destination IS the editor as of 0.52.0 — the
|
||||
// graph / text / run tabs all live behind one icon. The
|
||||
// separate "flow-editor" destination is gone; the Cmd+K
|
||||
// palette still navigates to flows when the operator
|
||||
// wants the editor surface.
|
||||
_NavPage(
|
||||
id: 'flows',
|
||||
icon: Icons.account_tree_outlined,
|
||||
selectedIcon: Icons.account_tree,
|
||||
page: FlowsPage(),
|
||||
),
|
||||
_NavPage(
|
||||
id: 'flow-editor',
|
||||
icon: Icons.code_outlined,
|
||||
selectedIcon: Icons.code,
|
||||
page: _FlowEditorAdapter(),
|
||||
),
|
||||
_NavPage(
|
||||
id: 'audit',
|
||||
icon: Icons.timeline_outlined,
|
||||
|
|
@ -1322,22 +1320,7 @@ class _LanguageToggle extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
/// Adapter that resolves the swappable FlowEditorPage's
|
||||
/// runtime dependencies (locale, hub onRun callback) from
|
||||
/// Studio's BuildContext. The _NavPage destination list
|
||||
/// holds widgets as const-ish, so the adapter sits in
|
||||
/// between to grab live data.
|
||||
class _FlowEditorAdapter extends StatelessWidget {
|
||||
const _FlowEditorAdapter();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final lang = Localizations.localeOf(context).languageCode;
|
||||
return FlowEditorPage(
|
||||
locale: lang == 'de' ? FlowEditorLocale.de : FlowEditorLocale.en,
|
||||
onRun: (name) async =>
|
||||
(await HubService.instance.runSavedFlow(name: name))
|
||||
.cast<String, Object>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// _FlowEditorAdapter removed in 0.52.0 — FlowsPage now hosts
|
||||
// the editor directly with the full runDriver bridge. There
|
||||
// is one Flows destination; the standalone editor route is
|
||||
// gone.
|
||||
|
|
|
|||
1463
lib/pages/flows.dart
1463
lib/pages/flows.dart
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue