The Run tab asked the operator to type an absolute path into a text field — with no file dialog anywhere. FlowEditorPage now accepts an onPickFile callback (PickedFileData: name + bytes) that hosts wire to their native picker; the path-entry dialog remains only as the no-host fallback. 0.23.0. Signed-off-by: flemming-it <sf@flemming.it>
216 lines
7.2 KiB
Dart
216 lines
7.2 KiB
Dart
// Host-supplied bridge between the editor and the hub.
|
|
//
|
|
// The editor package can't talk to the hub directly — that's
|
|
// the host's job (Studio uses the gRPC SDK, a CLI host could
|
|
// shell out to `fai run`, a test harness can stub everything
|
|
// in-memory). The bridge keeps the editor reusable across
|
|
// any host that can implement the two methods below.
|
|
//
|
|
// Lifecycle expected by the editor:
|
|
//
|
|
// 1. Subscribe to `events()` BEFORE calling `runFlow()`.
|
|
// The hub starts emitting `step.started` events the
|
|
// instant the flow begins; the host's subscription must
|
|
// already be live so the first one doesn't get lost.
|
|
// 2. Call `runFlow()` with the flow name + collected inputs.
|
|
// The returned Future resolves with the final outputs
|
|
// (or throws on failure).
|
|
// 3. Unsubscribe from `events()` when the run resolves.
|
|
|
|
import 'dart:typed_data';
|
|
|
|
/// A file chosen by the host's native picker for a flow file input.
|
|
class PickedFileData {
|
|
final String fileName;
|
|
final Uint8List bytes;
|
|
const PickedFileData({required this.fileName, required this.bytes});
|
|
}
|
|
|
|
/// Host-side native file picker. Returns null when the operator
|
|
/// cancels. When a host does not supply one, the Run tab falls back
|
|
/// to its manual path-entry dialog.
|
|
typedef PickFileCallback = Future<PickedFileData?> Function();
|
|
|
|
|
|
abstract class FlowRunDriver {
|
|
/// Trigger a run of [flowName] with the supplied inputs.
|
|
/// Text + file inputs come in separate maps so the host
|
|
/// can wire them to the right payload shape. Returns the
|
|
/// flow's final outputs map keyed by output name.
|
|
Future<Map<String, FlowOutputValue>> runFlow({
|
|
required String flowName,
|
|
required Map<String, String> textInputs,
|
|
required Map<String, Uint8List> fileInputs,
|
|
required Map<String, String> fileMimes,
|
|
});
|
|
|
|
/// Live event stream scoped to whatever the host can
|
|
/// observe — typically the hub's `StreamEvents` RPC
|
|
/// filtered to step.* event types for the running flow.
|
|
/// May arrive late (subscribe BEFORE calling runFlow!) and
|
|
/// may emit events for unrelated runs; the editor filters
|
|
/// by [FlowRunEvent.flowName] before applying.
|
|
Stream<FlowRunEvent> events();
|
|
|
|
/// Look up an installed module's manifest by capability
|
|
/// reference. The editor uses this to render per-field
|
|
/// input/output ports with their declared names + types +
|
|
/// tooltips, instead of collapsing every downstream reference
|
|
/// into a single output anchor.
|
|
///
|
|
/// Default returns `null` so legacy hosts that didn't update
|
|
/// their FlowRunDriver implementation keep compiling — the
|
|
/// editor falls back to the YAML-reference-derived ports.
|
|
/// A real implementation hits the hub's ModuleInfo RPC and
|
|
/// maps the result to [ModuleSpec].
|
|
Future<ModuleSpec?> moduleInfo(String capability) async => null;
|
|
|
|
/// Resolve the pending approval id for `(flowName, stepId)`
|
|
/// when the flow is paused on a `system.approval@^0` step.
|
|
/// Default returns `null` so legacy hosts that didn't update
|
|
/// keep compiling — the editor's Run tab then falls back to
|
|
/// directing the operator at the standalone Approvals page.
|
|
Future<String?> pendingApprovalIdForStep({
|
|
required String flowName,
|
|
required String stepId,
|
|
}) async =>
|
|
null;
|
|
|
|
/// Approve the named approval. `reviewer` is the audit-log
|
|
/// identity the host wants attributed (typically OS user +
|
|
/// '@studio'). Throws on hub-side failure; UI surfaces it.
|
|
Future<void> approveApproval({
|
|
required String approvalId,
|
|
required String reviewer,
|
|
}) async {
|
|
throw UnsupportedError(
|
|
'FlowRunDriver host did not implement approveApproval',
|
|
);
|
|
}
|
|
|
|
/// Reject the named approval with a free-form reason.
|
|
Future<void> rejectApproval({
|
|
required String approvalId,
|
|
required String reviewer,
|
|
required String reason,
|
|
}) async {
|
|
throw UnsupportedError(
|
|
'FlowRunDriver host did not implement rejectApproval',
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Declared inputs + outputs of one installed module, as seen
|
|
/// by the editor. The shape mirrors `ModuleInfoResponse.inputs`
|
|
/// / `outputs` over gRPC but without proto dependencies.
|
|
class ModuleSpec {
|
|
/// Capability identifier the host resolved (e.g. `text.summarize`).
|
|
final String capability;
|
|
|
|
/// Declared inputs, in stable alphabetical order. The editor
|
|
/// draws one input port per entry.
|
|
final List<ModuleField> inputs;
|
|
|
|
/// Declared outputs, in stable alphabetical order. The editor
|
|
/// draws one output port per entry — this is what makes
|
|
/// `summarize.response`, `summarize.model_endpoint`,
|
|
/// `summarize.model_name`, `summarize.model_digest` appear as
|
|
/// four distinct anchors instead of one.
|
|
final List<ModuleField> outputs;
|
|
|
|
const ModuleSpec({
|
|
required this.capability,
|
|
required this.inputs,
|
|
required this.outputs,
|
|
});
|
|
}
|
|
|
|
/// One declared input or output field. Carries the type
|
|
/// descriptor (`text` / `json` / `bytes` / `file`) and a locale
|
|
/// → description map for tooltip rendering.
|
|
class ModuleField {
|
|
/// Field name, e.g. `prompt` or `model_endpoint`.
|
|
final String name;
|
|
|
|
/// Type descriptor.
|
|
final String type;
|
|
|
|
/// IETF locale tag → description string. Empty when the
|
|
/// manifest used the shorthand form (no description).
|
|
final Map<String, String> description;
|
|
|
|
const ModuleField({
|
|
required this.name,
|
|
required this.type,
|
|
this.description = const {},
|
|
});
|
|
|
|
/// Description in [locale] with English fallback. Returns
|
|
/// null when the manifest carries no description for either.
|
|
String? descriptionFor(String locale) {
|
|
final exact = description[locale];
|
|
if (exact != null && exact.isNotEmpty) return exact;
|
|
final en = description['en'];
|
|
if (en != null && en.isNotEmpty) return en;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// What the editor needs out of a single event tick. Maps
|
|
/// 1:1 to the hub's `step.*` events but with the typing
|
|
/// projected into the editor's vocabulary so we don't carry
|
|
/// the SDK's proto types across the package boundary.
|
|
sealed class FlowRunEvent {
|
|
final String flowName;
|
|
final String stepId;
|
|
const FlowRunEvent({required this.flowName, required this.stepId});
|
|
}
|
|
|
|
class StepStarted extends FlowRunEvent {
|
|
const StepStarted({required super.flowName, required super.stepId});
|
|
}
|
|
|
|
class StepCompleted extends FlowRunEvent {
|
|
final int durationMs;
|
|
const StepCompleted({
|
|
required super.flowName,
|
|
required super.stepId,
|
|
required this.durationMs,
|
|
});
|
|
}
|
|
|
|
class StepFailed extends FlowRunEvent {
|
|
final String error;
|
|
const StepFailed({
|
|
required super.flowName,
|
|
required super.stepId,
|
|
required this.error,
|
|
});
|
|
}
|
|
|
|
class StepAwaitingApproval extends FlowRunEvent {
|
|
const StepAwaitingApproval({required super.flowName, required super.stepId});
|
|
}
|
|
|
|
/// One output value, after the run completed. The editor
|
|
/// renders text inline, JSON pretty-printed, bytes as a
|
|
/// download badge.
|
|
sealed class FlowOutputValue {
|
|
const FlowOutputValue();
|
|
}
|
|
|
|
class FlowOutputText extends FlowOutputValue {
|
|
final String value;
|
|
const FlowOutputText(this.value);
|
|
}
|
|
|
|
class FlowOutputJson extends FlowOutputValue {
|
|
final Object? value;
|
|
const FlowOutputJson(this.value);
|
|
}
|
|
|
|
class FlowOutputBytes extends FlowOutputValue {
|
|
final Uint8List value;
|
|
final String mimeType;
|
|
const FlowOutputBytes(this.value, this.mimeType);
|
|
}
|