feat: guided setup — free-text AI path (phase 1.2)
The wizard's first step now offers 'or just describe what you want
to do': the goal goes to the configured system AI, which maps it
onto the menu answers (validated against strict enum whitelists —
a hallucinated value can never reach the engine). The suggestion
comes back as an editable plain-language reflection ('this is how I
read your task') the operator can adjust step-by-step or take to
the same preview/apply the menu path uses. Trust rules per
guided-setup.md: suggestion only (never auto-apply), a privacy line
states whether the description is processed locally or sent to a
provider, and without a configured system AI the section explains
that the menu always works — no dead end.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
1f1e050b42
commit
09c901b75e
7 changed files with 619 additions and 2 deletions
|
|
@ -176,4 +176,88 @@ void main() {
|
|||
expect(find.text('text.extract installieren'), findsNothing);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'step 1 offers the free-text path with an honest no-AI fallback hint',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
locale: const Locale('de'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Builder(
|
||||
builder: (context) => Scaffold(
|
||||
body: Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) =>
|
||||
const GuidedSetupDialog(debugSkipAiProbe: true),
|
||||
),
|
||||
child: const Text('open'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.tap(find.text('open'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.text('Oder beschreiben Sie einfach, was Sie vorhaben'),
|
||||
findsOneWidget,
|
||||
);
|
||||
// No system AI configured → honest fallback: the menu always
|
||||
// works; no dead end, no disabled mystery button.
|
||||
expect(
|
||||
find.textContaining('Die Auswahl oben funktioniert immer'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('Vorschlagen lassen'), findsNothing);
|
||||
},
|
||||
);
|
||||
|
||||
group('parseSetupSuggestion', () {
|
||||
test('accepts a valid JSON object embedded in prose', () {
|
||||
final r = parseSetupSuggestion(
|
||||
'Sure! Here is the mapping:\n'
|
||||
'{"scenario":"regulated-production","intent":"classify-documents",'
|
||||
'"target":"home-server","require_approval":true,'
|
||||
'"data_must_stay_local":false}\n'
|
||||
'Let me know if you need anything else.',
|
||||
);
|
||||
expect(r, isNotNull);
|
||||
expect(r!['scenario'], 'regulated-production');
|
||||
expect(r['intent'], 'classify-documents');
|
||||
expect(r['target'], 'home-server');
|
||||
expect(r['require_approval'], true);
|
||||
expect(r['data_must_stay_local'], false);
|
||||
});
|
||||
|
||||
test('rejects hallucinated enum values', () {
|
||||
expect(
|
||||
parseSetupSuggestion(
|
||||
'{"scenario":"world-domination","intent":"hello-world",'
|
||||
'"target":"this-laptop"}',
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects non-JSON replies', () {
|
||||
expect(parseSetupSuggestion('I cannot help with that.'), isNull);
|
||||
expect(parseSetupSuggestion('{broken json'), isNull);
|
||||
});
|
||||
|
||||
test('missing booleans default to false', () {
|
||||
final r = parseSetupSuggestion(
|
||||
'{"scenario":"trying-out","intent":"hello-world",'
|
||||
'"target":"this-laptop"}',
|
||||
);
|
||||
expect(r, isNotNull);
|
||||
expect(r!['require_approval'], false);
|
||||
expect(r['data_must_stay_local'], false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue