feat(studio): Flows + Module sheet + System-AI editor i18n (v0.28.0)
- Localize Flows page: app-bar title and reload tooltip,
hub-unreachable / no-saved-flows empty states, run-flow input
dialog (title with flow name, description, hint, Cancel /
Run), running dialog (title, "Flow running…" status, no-output
message, Close button), flow-card Run button.
- Localize the module-sheet bottom sheet: failed-to-load text,
Capabilities and "Declared permissions" section headers,
no-permissions placeholder copy.
- Localize the System-AI configuration dialog: title, intro
paragraph, provider dropdown label, endpoint label, API-key
env-var label (required vs optional) and disclaimer, privacy
mode header and three options (Off / Redacted / Full) plus
their descriptions, test-result panel ("Connection ok" /
"Connection failed", "Reply: …" prefix), model picker (label,
hint fallback, helper text with Ollama variant, Refresh /
Pull / Pulling… buttons, list errors and empty states),
hardware banner ("Detected: …" + " · curation reviewed …"
suffix), suitability legend (recommended / balanced / small /
large / huge / unknown), cache row with pluralized count and
Clear button, cache cleared / clear-failed toasts, pull-empty
/ pull-failed errors. Suitability label moved off a getter
onto a `labelFor(BuildContext)` method so it can read the
current locale.
Provider preset descriptions and modelHint strings stay in
English in `_ProviderPreset.all` — they're tightly coupled to
the wire-protocol identifiers and would require a runtime
factory rebuild rather than a const list.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
e2b2639a86
commit
b5a4c74c4b
10 changed files with 1120 additions and 91 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../data/hub.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../theme/theme.dart';
|
||||
import '../theme/tokens.dart';
|
||||
import '../widgets/widgets.dart';
|
||||
|
|
@ -38,14 +39,15 @@ class _FlowsPageState extends State<FlowsPage> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l = AppLocalizations.of(context)!;
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: AppBar(
|
||||
title: const Text('Flows'),
|
||||
title: Text(l.flowsTitle),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh, size: 18),
|
||||
tooltip: 'Reload',
|
||||
tooltip: l.flowsReloadTooltip,
|
||||
onPressed: _refresh,
|
||||
),
|
||||
const SizedBox(width: FaiSpace.sm),
|
||||
|
|
@ -61,21 +63,20 @@ class _FlowsPageState extends State<FlowsPage> {
|
|||
return FaiEmptyState(
|
||||
icon: Icons.cloud_off_outlined,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
title: 'Hub unreachable',
|
||||
hint: 'Start the hub with `fai serve`.',
|
||||
title: l.hubUnreachable,
|
||||
hint: l.hubUnreachableHint,
|
||||
action: FilledButton.tonal(
|
||||
onPressed: _refresh,
|
||||
child: const Text('Retry'),
|
||||
child: Text(l.buttonRetry),
|
||||
),
|
||||
);
|
||||
}
|
||||
final flows = snapshot.data ?? [];
|
||||
if (flows.isEmpty) {
|
||||
return const FaiEmptyState(
|
||||
return FaiEmptyState(
|
||||
icon: Icons.account_tree_outlined,
|
||||
title: 'No saved flows',
|
||||
hint:
|
||||
'Save a flow with `fai admin flows save <name> <flow.yaml>` and it shows up here.',
|
||||
title: l.flowsNoneTitle,
|
||||
hint: l.flowsNoneHint,
|
||||
);
|
||||
}
|
||||
return ListView.separated(
|
||||
|
|
@ -139,7 +140,7 @@ class _FlowCard extends StatelessWidget {
|
|||
FilledButton.icon(
|
||||
onPressed: onRun,
|
||||
icon: const Icon(Icons.play_arrow, size: 16),
|
||||
label: const Text('Run'),
|
||||
label: Text(AppLocalizations.of(context)!.flowsRunButton),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -194,8 +195,9 @@ class _FlowInputDialogState extends State<_FlowInputDialog> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final l = AppLocalizations.of(context)!;
|
||||
return AlertDialog(
|
||||
title: Text('Run ${widget.flow.name}'),
|
||||
title: Text(l.flowsRunDialogTitle(widget.flow.name)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
||||
),
|
||||
|
|
@ -206,7 +208,7 @@ class _FlowInputDialogState extends State<_FlowInputDialog> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Inputs as key=value, one per line. All values are sent as text payloads. For binary inputs use the `fai run` CLI.',
|
||||
l.flowsRunDialogBody,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
|
|
@ -219,10 +221,9 @@ class _FlowInputDialogState extends State<_FlowInputDialog> {
|
|||
expands: true,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
style: FaiTheme.mono(size: 12),
|
||||
decoration: const InputDecoration(
|
||||
hintText:
|
||||
'name=World\ntarget_language=English\nsummary_style=three bullet points',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
hintText: l.flowsInputsHint,
|
||||
border: const OutlineInputBorder(),
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
|
|
@ -233,11 +234,11 @@ class _FlowInputDialogState extends State<_FlowInputDialog> {
|
|||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, null),
|
||||
child: const Text('Cancel'),
|
||||
child: Text(l.buttonCancel),
|
||||
),
|
||||
FilledButton.icon(
|
||||
icon: const Icon(Icons.play_arrow, size: 16),
|
||||
label: const Text('Run'),
|
||||
label: Text(l.flowsRunButton),
|
||||
onPressed: () => Navigator.pop(context, _parsePairs()),
|
||||
),
|
||||
],
|
||||
|
|
@ -270,8 +271,9 @@ class _FlowRunDialogState extends State<_FlowRunDialog> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final l = AppLocalizations.of(context)!;
|
||||
return AlertDialog(
|
||||
title: Text('Running ${widget.flow.name}'),
|
||||
title: Text(l.flowsRunningTitle(widget.flow.name)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
||||
),
|
||||
|
|
@ -288,7 +290,7 @@ class _FlowRunDialogState extends State<_FlowRunDialog> {
|
|||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: FaiSpace.md),
|
||||
Text(
|
||||
'Flow running…',
|
||||
l.flowsRunning,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
|
|
@ -322,7 +324,7 @@ class _FlowRunDialogState extends State<_FlowRunDialog> {
|
|||
final outputs = snapshot.data ?? const {};
|
||||
if (outputs.isEmpty) {
|
||||
return Text(
|
||||
'Flow completed with no declared outputs.',
|
||||
l.flowsNoOutputs,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
);
|
||||
}
|
||||
|
|
@ -368,7 +370,7 @@ class _FlowRunDialogState extends State<_FlowRunDialog> {
|
|||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Close'),
|
||||
child: Text(l.buttonClose),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue