From d33d9c502bda25477cbcc2e1a5492ea1aa54c393 Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Sun, 27 Jul 2025 14:38:29 -0700 Subject: [PATCH 1/8] - --- pkgs/example/README.md | 6 ++ pkgs/example/lib/sdk/agent/fake_output.dart | 13 ++++ .../lib/sdk/catalog/elements/filter.dart | 73 +++++++++++++++++++ .../lib/sdk/catalog/messages/elicitation.dart | 6 +- 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 pkgs/example/lib/sdk/catalog/elements/filter.dart diff --git a/pkgs/example/README.md b/pkgs/example/README.md index 90e2c6601..09dbcb31d 100644 --- a/pkgs/example/README.md +++ b/pkgs/example/README.md @@ -10,3 +10,9 @@ To regenerate diagrams: dart pub global activate layerlens layerlens ``` + +## TODO + +TODO before productizing: + +1. Make colors and sizes configurable. diff --git a/pkgs/example/lib/sdk/agent/fake_output.dart b/pkgs/example/lib/sdk/agent/fake_output.dart index b7f845216..a0d134210 100644 --- a/pkgs/example/lib/sdk/agent/fake_output.dart +++ b/pkgs/example/lib/sdk/agent/fake_output.dart @@ -1,4 +1,7 @@ +import 'package:flutter/material.dart'; + import '../catalog/elements/carousel.dart'; +import '../catalog/elements/filter.dart'; import '../catalog/elements/text_intro.dart'; import '../catalog/messages/elicitation.dart'; import '../catalog/messages/invitation.dart'; @@ -34,4 +37,14 @@ final fakeElicitationData = ElicitationData( textIntroData: TextIntroData( intro: 'OK I can help generate itinerary as follows or tap to edit', ), + filterData: FilterData([ + FilterItemData(label: 'Zermatt', icon: Icons.location_pin), + FilterItemData(label: '3 days', icon: Icons.calendar_month), + FilterItemData( + label: '2 adults + 1 child', + icon: Icons.supervised_user_circle_outlined, + ), + FilterItemData(label: 'Low cost', icon: Icons.money), + FilterItemData(label: 'Medium activity', icon: Icons.run_circle_sharp), + ], submitLabel: 'Generate'), ); diff --git a/pkgs/example/lib/sdk/catalog/elements/filter.dart b/pkgs/example/lib/sdk/catalog/elements/filter.dart new file mode 100644 index 000000000..be7b263be --- /dev/null +++ b/pkgs/example/lib/sdk/catalog/elements/filter.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; + +import '../../model/simple_items.dart'; + +class Filter extends StatefulWidget { + const Filter(this.data, {super.key}); + + final FilterData data; + + @override + State createState() => _FilterState(); +} + +class _FilterState extends State { + @override + Widget build(BuildContext context) { + return Card( + color: Theme.of(context).colorScheme.primaryContainer, + + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + runSpacing: 8.0, + spacing: 8.0, + children: widget.data.items.map(FilterItem.new).toList(), + ), + const SizedBox(height: 16.0), + ElevatedButton( + onPressed: () {}, + child: Text(widget.data.submitLabel), + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Colors.white, + ), + ), + ], + ), + ), + ); + } +} + +class FilterItem extends StatelessWidget { + const FilterItem(this.data, {super.key}); + + final FilterItemData data; + + @override + Widget build(BuildContext context) { + return ElevatedButton.icon( + icon: Icon(data.icon), // The icon to display + label: Text(data.label), // The text label for the button + onPressed: () {}, + ); + } +} + +class FilterData implements WidgetData { + final List items; + final String submitLabel; + + FilterData(this.items, {required this.submitLabel}); +} + +class FilterItemData implements WidgetData { + final String label; + final IconData icon; + + FilterItemData({required this.label, required this.icon}); +} diff --git a/pkgs/example/lib/sdk/catalog/messages/elicitation.dart b/pkgs/example/lib/sdk/catalog/messages/elicitation.dart index a00d56652..f55e9b632 100644 --- a/pkgs/example/lib/sdk/catalog/messages/elicitation.dart +++ b/pkgs/example/lib/sdk/catalog/messages/elicitation.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../../model/agent.dart'; import '../../model/input.dart'; import '../../model/simple_items.dart'; +import '../elements/filter.dart'; import '../elements/text_intro.dart'; import '../shared/genui_widget.dart'; import '../shared/text_styles.dart'; @@ -29,7 +30,7 @@ class _ElicitationState extends State { const SizedBox(height: 8.0), TextIntro(widget.data.textIntroData), const SizedBox(height: 16.0), - Text('filter will be here', style: GenUiTextStyles.h2(context)), + Filter(widget.data.filterData), const SizedBox(height: 16.0), ValueListenableBuilder( @@ -50,6 +51,7 @@ class _ElicitationState extends State { class ElicitationData extends WidgetData { final TextIntroData textIntroData; + final FilterData filterData; - ElicitationData({required this.textIntroData}); + ElicitationData({required this.filterData, required this.textIntroData}); } From f2507be25b8f18f3158ef65278d6d18f2fe63115 Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Sun, 27 Jul 2025 20:35:57 -0700 Subject: [PATCH 2/8] - --- pkgs/example/lib/sdk/agent/agent.dart | 21 +++++++++------- .../lib/sdk/catalog/messages/invitation.dart | 8 +----- .../lib/sdk/catalog/shared/genui_widget.dart | 25 +++++++++++++------ pkgs/example/lib/sdk/model/agent.dart | 17 +++++++++++-- 4 files changed, 45 insertions(+), 26 deletions(-) diff --git a/pkgs/example/lib/sdk/agent/agent.dart b/pkgs/example/lib/sdk/agent/agent.dart index aa3f63e64..7fbadfe19 100644 --- a/pkgs/example/lib/sdk/agent/agent.dart +++ b/pkgs/example/lib/sdk/agent/agent.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/widgets.dart'; import '../catalog/messages/elicitation.dart'; @@ -10,31 +12,32 @@ import 'fake_output.dart'; class SimpleGenUiAgent extends GenUiAgent { SimpleGenUiAgent(super.controller); - final List<({Input input, WidgetData output})> _history = []; + final List _history = []; + + Future _request(Input input) async { + round.input.complete(input); - @override - Future request(Input input) async { // Simulate network delay await Future.delayed(const Duration(milliseconds: 1000)); late final WidgetData output; - late final WidgetBuilder result; + late final WidgetBuilder builder; switch (input) { case InitialInput(): output = fakeInvitationData; - result = (_) => Invitation(fakeInvitationData, this); + builder = (_) => Invitation(fakeInvitationData, this); case ChatBoxInput(): output = fakeElicitationData; - result = (_) => Elicitation(fakeElicitationData, this); + builder = (_) => Elicitation(fakeElicitationData, this); default: throw UnimplementedError( 'The agent does not support input of type ${input.runtimeType}', ); } - _history.add((input: input, output: output)); - - return result; + round.output.complete(output); + round.builder.complete(builder); + _history.add(round); } } diff --git a/pkgs/example/lib/sdk/catalog/messages/invitation.dart b/pkgs/example/lib/sdk/catalog/messages/invitation.dart index 7e840c20f..727dcf630 100644 --- a/pkgs/example/lib/sdk/catalog/messages/invitation.dart +++ b/pkgs/example/lib/sdk/catalog/messages/invitation.dart @@ -36,13 +36,7 @@ class _InvitationState extends State { Text(widget.data.exploreTitle, style: GenUiTextStyles.h2(context)), Carousel(CarouselData(items: widget.data.exploreItems), onInput), const SizedBox(height: 16.0), - ChatBox( - onInput, - fakeInput: - 'I have 3 days in Zermatt with my wife and 11 year old daughter, ' - 'and I am wondering how to make the most out of our time.', - ), - const SizedBox(height: 28.0), + GenUiWidget.wait(_input, widget.agent), ], ); diff --git a/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart b/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart index 92619df30..28c96e5ab 100644 --- a/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart +++ b/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart @@ -4,14 +4,15 @@ import 'package:flutter/material.dart'; import '../../model/agent.dart'; import '../../model/input.dart'; +import '../elements/chat_box.dart'; class GenUiWidget extends StatefulWidget { - factory GenUiWidget(Input input, GenUiAgent agent) => - GenUiWidget.wait(Completer()..complete(input), agent); - - const GenUiWidget.wait(this.input, this.agent, {super.key}); + GenUiWidget(this.agent, {Input? input} ) { + if (input != null) { + agent.round.input.complete(input); + } + } - final Completer input; final GenUiAgent agent; @override @@ -29,9 +30,9 @@ class _GenUiWidgetState extends State { } Future _initialize() async { - final input = await widget.input.future; + final input = await widget.agent.round.input.future; setState(() => _input = input); - final builder = await widget.agent.request(input); + await widget.agent.request(input); setState(() => _builder = builder); // Scroll to the bottom after the widget is built @@ -46,7 +47,15 @@ class _GenUiWidgetState extends State { @override Widget build(BuildContext context) { - if (_input == null) return const SizedBox.shrink(); + if (_input == null) { + return ChatBox( + onInput, + // fakeInput: + // 'I have 3 days in Zermatt with my wife and 11 year old daughter, ' + // 'and I am wondering how to make the most out of our time.', + ); + const SizedBox(height: 28.0), + } final builder = _builder; if (builder == null) { return const Center(child: CircularProgressIndicator()); diff --git a/pkgs/example/lib/sdk/model/agent.dart b/pkgs/example/lib/sdk/model/agent.dart index b614e7ed8..0d94c1efb 100644 --- a/pkgs/example/lib/sdk/model/agent.dart +++ b/pkgs/example/lib/sdk/model/agent.dart @@ -1,18 +1,31 @@ +import 'dart:async'; + import 'package:flutter/widgets.dart'; import 'controller.dart'; import 'input.dart'; +import 'simple_items.dart'; abstract class GenUiAgent { final GenUiController controller; GenUiAgent(this.controller); - Future request(Input input); + void submitInput(Input input); + + /// The current round of interaction with the agent. + /// + /// Should be mutated only by agent methods. + /// TODO: make immutable. + GenUiRound round = GenUiRound(); Widget icon({double? width, double? height}) { return Image.asset(width: 40, height: 40, controller.agentIconAsset); } +} - void dispose() {} +class GenUiRound { + final input = Completer(); + final output = Completer(); + final Completer builder = Completer(); } From e160321362e53f7d8e7a3cdae180c9d91ee2f77d Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Mon, 28 Jul 2025 11:21:08 -0700 Subject: [PATCH 3/8] - --- pkgs/example/lib/sdk/agent/agent.dart | 55 ++++++++++++++----- .../lib/sdk/catalog/elements/chat_box.dart | 12 +++- .../lib/sdk/catalog/messages/invitation.dart | 1 - .../lib/sdk/catalog/shared/genui_widget.dart | 52 +++++++++--------- pkgs/example/lib/sdk/model/agent.dart | 31 ----------- pkgs/example/lib/sdk/model/controller.dart | 25 +++++++++ pkgs/example/lib/sdk/primitives/utils.dart | 9 +++ 7 files changed, 112 insertions(+), 73 deletions(-) delete mode 100644 pkgs/example/lib/sdk/model/agent.dart create mode 100644 pkgs/example/lib/sdk/primitives/utils.dart diff --git a/pkgs/example/lib/sdk/agent/agent.dart b/pkgs/example/lib/sdk/agent/agent.dart index 7fbadfe19..7176c4163 100644 --- a/pkgs/example/lib/sdk/agent/agent.dart +++ b/pkgs/example/lib/sdk/agent/agent.dart @@ -4,31 +4,44 @@ import 'package:flutter/widgets.dart'; import '../catalog/messages/elicitation.dart'; import '../catalog/messages/invitation.dart'; -import '../model/agent.dart'; +import '../model/controller.dart'; import '../model/input.dart'; import '../model/simple_items.dart'; +import '../primitives/utils.dart'; import 'fake_output.dart'; -class SimpleGenUiAgent extends GenUiAgent { - SimpleGenUiAgent(super.controller); +class GenUiAgent { + GenUiAgent(this.controller); - final List _history = []; + GenUiController controller; - Future _request(Input input) async { - round.input.complete(input); + void run() => _startCycle(); - // Simulate network delay + Future _startCycle() async { + while (true) { + await _handleNextInput(); + } + } + + void dispose() { + // TODO: stop cycle + } + + Future _handleNextInput() async { + final input = await controller.state.input.future; + + // Simulate network delay. await Future.delayed(const Duration(milliseconds: 1000)); - late final WidgetData output; + late final WidgetData data; late final WidgetBuilder builder; switch (input) { case InitialInput(): - output = fakeInvitationData; + data = fakeInvitationData; builder = (_) => Invitation(fakeInvitationData, this); case ChatBoxInput(): - output = fakeElicitationData; + data = fakeElicitationData; builder = (_) => Elicitation(fakeElicitationData, this); default: throw UnimplementedError( @@ -36,8 +49,24 @@ class SimpleGenUiAgent extends GenUiAgent { ); } - round.output.complete(output); - round.builder.complete(builder); - _history.add(round); + final newInput = await controller.state.input.future; + if (newInput != input) { + // If the input has changed, we throw away the results. + return; + } + + // Provide the builder for the widget that wait for it. + controller.state.builder.complete(builder); + + // Move the input and data to the history. + controller.state.history.add((input: input, data: data)); + + // Reset the input completer for the next input. + controller.state.input = Completer(); + controller.state.builder = Completer(); + + // Scroll to the bottom after the widget is built + await Future.delayed(const Duration(milliseconds: 200)); + await scrollToBottom(controller.scrollController); } } diff --git a/pkgs/example/lib/sdk/catalog/elements/chat_box.dart b/pkgs/example/lib/sdk/catalog/elements/chat_box.dart index 19d2f8b7f..f50212eb9 100644 --- a/pkgs/example/lib/sdk/catalog/elements/chat_box.dart +++ b/pkgs/example/lib/sdk/catalog/elements/chat_box.dart @@ -2,14 +2,16 @@ import 'package:flutter/material.dart'; import '../../model/input.dart'; class ChatBox extends StatefulWidget { - ChatBox(this.onInput, {super.key, this.fakeInput = ''}); + ChatBox(this.onInput, {super.key}); final UserInputCallback onInput; /// Fake input to simulate pre-filled text in the chat box. /// /// TODO(polina-c): Remove this in productized version. - final String fakeInput; + final String fakeInput = + 'I have 3 days in Zermatt with my wife and 11 year old daughter, ' + 'and I am wondering how to make the most out of our time.'; @override State createState() => _ChatBoxState(); @@ -24,7 +26,11 @@ class _ChatBoxState extends State { void initState() { super.initState(); _focusNode.addListener(() { - if (widget.fakeInput.isNotEmpty) { + // Reset the input on focus. + if (widget.fakeInput.isNotEmpty && + !_isSubmitted && + _focusNode.hasFocus && + _controller.text.isEmpty) { setState(() => _controller.text = widget.fakeInput); } }); diff --git a/pkgs/example/lib/sdk/catalog/messages/invitation.dart b/pkgs/example/lib/sdk/catalog/messages/invitation.dart index 727dcf630..5663149c2 100644 --- a/pkgs/example/lib/sdk/catalog/messages/invitation.dart +++ b/pkgs/example/lib/sdk/catalog/messages/invitation.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import '../../model/agent.dart'; import '../../model/input.dart'; import '../../model/simple_items.dart'; import '../elements/carousel.dart'; diff --git a/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart b/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart index 28c96e5ab..371b2ccd3 100644 --- a/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart +++ b/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart @@ -2,18 +2,18 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import '../../model/agent.dart'; +import '../../model/controller.dart'; import '../../model/input.dart'; import '../elements/chat_box.dart'; class GenUiWidget extends StatefulWidget { - GenUiWidget(this.agent, {Input? input} ) { + GenUiWidget(this.controller, {Input? input}) { if (input != null) { - agent.round.input.complete(input); + controller.state.input.complete(input); } } - final GenUiAgent agent; + final GenUiController controller; @override State createState() => _GenUiWidgetState(); @@ -30,36 +30,38 @@ class _GenUiWidgetState extends State { } Future _initialize() async { - final input = await widget.agent.round.input.future; + final state = widget.controller.state; + + final input = await state.input.future; setState(() => _input = input); - await widget.agent.request(input); + final builder = await state.builder.future; setState(() => _builder = builder); - - // Scroll to the bottom after the widget is built - await Future.delayed(const Duration(milliseconds: 200)); - final scroll = widget.agent.controller.scrollController; - await scroll.animateTo( - scroll.position.maxScrollExtent, - duration: const Duration(milliseconds: 600), - curve: Curves.fastOutSlowIn, - ); } @override Widget build(BuildContext context) { - if (_input == null) { - return ChatBox( - onInput, - // fakeInput: - // 'I have 3 days in Zermatt with my wife and 11 year old daughter, ' - // 'and I am wondering how to make the most out of our time.', - ); - const SizedBox(height: 28.0), - } + if (_input == null) return _buildChatBox(); + final builder = _builder; + if (builder == null) { return const Center(child: CircularProgressIndicator()); } - return builder(context); + + return Column( + children: [ + builder(context), + const SizedBox(height: 16.0), + _buildChatBox(), + ], + ); + } + + void _onInput(UserInput input) { + widget.controller.state.input.complete(input); + widget.controller.state.builder = Completer(); + _initialize(); } + + Widget _buildChatBox() => ChatBox(_onInput); } diff --git a/pkgs/example/lib/sdk/model/agent.dart b/pkgs/example/lib/sdk/model/agent.dart deleted file mode 100644 index 0d94c1efb..000000000 --- a/pkgs/example/lib/sdk/model/agent.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/widgets.dart'; - -import 'controller.dart'; -import 'input.dart'; -import 'simple_items.dart'; - -abstract class GenUiAgent { - final GenUiController controller; - - GenUiAgent(this.controller); - - void submitInput(Input input); - - /// The current round of interaction with the agent. - /// - /// Should be mutated only by agent methods. - /// TODO: make immutable. - GenUiRound round = GenUiRound(); - - Widget icon({double? width, double? height}) { - return Image.asset(width: 40, height: 40, controller.agentIconAsset); - } -} - -class GenUiRound { - final input = Completer(); - final output = Completer(); - final Completer builder = Completer(); -} diff --git a/pkgs/example/lib/sdk/model/controller.dart b/pkgs/example/lib/sdk/model/controller.dart index 271f1c71e..669b82664 100644 --- a/pkgs/example/lib/sdk/model/controller.dart +++ b/pkgs/example/lib/sdk/model/controller.dart @@ -1,11 +1,22 @@ +import 'dart:async'; + import 'package:flutter/widgets.dart'; +import 'input.dart'; import 'simple_items.dart'; +typedef InputCallback = void Function(Input input); + class GenUiController { final ImageCatalog imageCatalog; final String agentIconAsset; + final ScrollController scrollController; + final GenUiState state = GenUiState(); + + Widget icon({double? width, double? height}) { + return Image.asset(width: width, height: height, agentIconAsset); + } GenUiController( this.scrollController, { @@ -13,3 +24,17 @@ class GenUiController { required this.agentIconAsset, }); } + +/// Controller for the GenUi operations. +/// +/// TODO (polina-c): protect the fields from being mutated by the user. +/// +/// TODO (polina-c): handle race conditions when the input is changed +/// while the agent is processing it. +class GenUiState { + Completer input = Completer(); + + Completer builder = Completer(); + + final List<({Input input, WidgetData data})> history = []; +} diff --git a/pkgs/example/lib/sdk/primitives/utils.dart b/pkgs/example/lib/sdk/primitives/utils.dart new file mode 100644 index 000000000..b412d5f3f --- /dev/null +++ b/pkgs/example/lib/sdk/primitives/utils.dart @@ -0,0 +1,9 @@ +import 'package:flutter/widgets.dart'; + +Future scrollToBottom(ScrollController controller) async { + await controller.animateTo( + controller.position.maxScrollExtent, + duration: const Duration(milliseconds: 600), + curve: Curves.fastOutSlowIn, + ); +} From 42f8f73872106330c62942a698252bdc8d623952 Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Mon, 28 Jul 2025 13:17:09 -0700 Subject: [PATCH 4/8] - --- pkgs/example/lib/app/app.dart | 12 +++--------- pkgs/example/lib/sdk/agent/agent.dart | 4 ++-- .../lib/sdk/catalog/messages/elicitation.dart | 17 +++++------------ .../lib/sdk/catalog/messages/invitation.dart | 10 +++++----- .../lib/sdk/catalog/shared/genui_widget.dart | 6 +----- 5 files changed, 16 insertions(+), 33 deletions(-) diff --git a/pkgs/example/lib/app/app.dart b/pkgs/example/lib/app/app.dart index 007dabe35..a1c732fff 100644 --- a/pkgs/example/lib/app/app.dart +++ b/pkgs/example/lib/app/app.dart @@ -2,9 +2,8 @@ import 'package:flutter/material.dart'; import '../sdk/agent/agent.dart'; import '../sdk/catalog/shared/genui_widget.dart'; -import '../sdk/model/agent.dart'; + import '../sdk/model/controller.dart'; -import '../sdk/model/input.dart'; import '../sdk/model/simple_items.dart'; class MyApp extends StatelessWidget { @@ -36,7 +35,7 @@ class _MyHomePage extends StatefulWidget { class _MyHomePageState extends State<_MyHomePage> { final _scrollController = ScrollController(); - late final GenUiAgent _agent = SimpleGenUiAgent( + late final GenUiAgent _agent = GenUiAgent( GenUiController( _scrollController, imageCatalog: _myImageCatalog, @@ -63,12 +62,7 @@ class _MyHomePageState extends State<_MyHomePage> { child: Center( child: SingleChildScrollView( controller: _scrollController, - child: GenUiWidget( - InitialInput( - 'Invite user to create a vacation travel itinerary.', - ), - _agent, - ), + child: GenUiWidget(_agent.controller), ), ), ), diff --git a/pkgs/example/lib/sdk/agent/agent.dart b/pkgs/example/lib/sdk/agent/agent.dart index 7176c4163..6d2e6bb31 100644 --- a/pkgs/example/lib/sdk/agent/agent.dart +++ b/pkgs/example/lib/sdk/agent/agent.dart @@ -39,10 +39,10 @@ class GenUiAgent { switch (input) { case InitialInput(): data = fakeInvitationData; - builder = (_) => Invitation(fakeInvitationData, this); + builder = (_) => Invitation(fakeInvitationData, controller); case ChatBoxInput(): data = fakeElicitationData; - builder = (_) => Elicitation(fakeElicitationData, this); + builder = (_) => Elicitation(fakeElicitationData, controller); default: throw UnimplementedError( 'The agent does not support input of type ${input.runtimeType}', diff --git a/pkgs/example/lib/sdk/catalog/messages/elicitation.dart b/pkgs/example/lib/sdk/catalog/messages/elicitation.dart index f55e9b632..1c37fa42b 100644 --- a/pkgs/example/lib/sdk/catalog/messages/elicitation.dart +++ b/pkgs/example/lib/sdk/catalog/messages/elicitation.dart @@ -1,18 +1,17 @@ import 'package:flutter/material.dart'; -import '../../model/agent.dart'; +import '../../model/controller.dart'; import '../../model/input.dart'; import '../../model/simple_items.dart'; import '../elements/filter.dart'; import '../elements/text_intro.dart'; import '../shared/genui_widget.dart'; -import '../shared/text_styles.dart'; class Elicitation extends StatefulWidget { final ElicitationData data; - final GenUiAgent agent; + final GenUiController controller; - const Elicitation(this.data, this.agent, {super.key}); + const Elicitation(this.data, this.controller, {super.key}); @override State createState() => _ElicitationState(); @@ -26,20 +25,14 @@ class _ElicitationState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - widget.agent.icon(width: 40, height: 40), + widget.controller.icon(width: 40, height: 40), const SizedBox(height: 8.0), TextIntro(widget.data.textIntroData), const SizedBox(height: 16.0), Filter(widget.data.filterData), const SizedBox(height: 16.0), - ValueListenableBuilder( - valueListenable: _input, - builder: (context, input, child) { - if (input == null) return const SizedBox.shrink(); - return GenUiWidget(input, widget.agent); - }, - ), + GenUiWidget(widget.controller), ], ); } diff --git a/pkgs/example/lib/sdk/catalog/messages/invitation.dart b/pkgs/example/lib/sdk/catalog/messages/invitation.dart index 5663149c2..4a3e2589b 100644 --- a/pkgs/example/lib/sdk/catalog/messages/invitation.dart +++ b/pkgs/example/lib/sdk/catalog/messages/invitation.dart @@ -2,19 +2,19 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import '../../model/controller.dart'; import '../../model/input.dart'; import '../../model/simple_items.dart'; import '../elements/carousel.dart'; -import '../elements/chat_box.dart'; import '../elements/text_intro.dart'; import '../shared/genui_widget.dart'; import '../shared/text_styles.dart'; class Invitation extends StatefulWidget { final InvitationData data; - final GenUiAgent agent; + final GenUiController genUi; - const Invitation(this.data, this.agent, {super.key}); + const Invitation(this.data, this.genUi, {super.key}); @override State createState() => _InvitationState(); @@ -28,7 +28,7 @@ class _InvitationState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - widget.agent.icon(width: 40, height: 40), + widget.genUi.icon(width: 40, height: 40), const SizedBox(height: 8.0), TextIntro(widget.data.textIntroData), const SizedBox(height: 16.0), @@ -36,7 +36,7 @@ class _InvitationState extends State { Carousel(CarouselData(items: widget.data.exploreItems), onInput), const SizedBox(height: 16.0), - GenUiWidget.wait(_input, widget.agent), + GenUiWidget(widget.genUi), ], ); } diff --git a/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart b/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart index 371b2ccd3..6b22abda2 100644 --- a/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart +++ b/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart @@ -7,11 +7,7 @@ import '../../model/input.dart'; import '../elements/chat_box.dart'; class GenUiWidget extends StatefulWidget { - GenUiWidget(this.controller, {Input? input}) { - if (input != null) { - controller.state.input.complete(input); - } - } + GenUiWidget(this.controller); final GenUiController controller; From f8c656148ec2c51bba307137fce4a3d7e02d6123 Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Mon, 28 Jul 2025 13:27:51 -0700 Subject: [PATCH 5/8] Update app.dart --- pkgs/example/lib/app/app.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/example/lib/app/app.dart b/pkgs/example/lib/app/app.dart index a1c732fff..7333834d1 100644 --- a/pkgs/example/lib/app/app.dart +++ b/pkgs/example/lib/app/app.dart @@ -41,7 +41,7 @@ class _MyHomePageState extends State<_MyHomePage> { imageCatalog: _myImageCatalog, agentIconAsset: 'assets/agent_icon.png', ), - ); + )..run(); @override Widget build(BuildContext context) { From 2522f8896e04d39a064c8f4f9faf9f932b00d616 Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Mon, 28 Jul 2025 13:30:16 -0700 Subject: [PATCH 6/8] - --- pkgs/example/lib/app/app.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkgs/example/lib/app/app.dart b/pkgs/example/lib/app/app.dart index 7333834d1..bc02726a5 100644 --- a/pkgs/example/lib/app/app.dart +++ b/pkgs/example/lib/app/app.dart @@ -4,6 +4,7 @@ import '../sdk/agent/agent.dart'; import '../sdk/catalog/shared/genui_widget.dart'; import '../sdk/model/controller.dart'; +import '../sdk/model/input.dart'; import '../sdk/model/simple_items.dart'; class MyApp extends StatelessWidget { @@ -43,6 +44,13 @@ class _MyHomePageState extends State<_MyHomePage> { ), )..run(); + @override + void initState() { + super.initState(); + + _agent.controller.state.input.complete(InitialInput()); + } + @override Widget build(BuildContext context) { return Scaffold( From 99c2bd3d7ff63a762f724a9903192ee1a7c717ec Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Mon, 28 Jul 2025 13:31:36 -0700 Subject: [PATCH 7/8] Update app.dart --- pkgs/example/lib/app/app.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/example/lib/app/app.dart b/pkgs/example/lib/app/app.dart index bc02726a5..aac8c41af 100644 --- a/pkgs/example/lib/app/app.dart +++ b/pkgs/example/lib/app/app.dart @@ -48,7 +48,9 @@ class _MyHomePageState extends State<_MyHomePage> { void initState() { super.initState(); - _agent.controller.state.input.complete(InitialInput()); + _agent.controller.state.input.complete( + InitialInput('Show invitations to create a vacation travel itinerary.'), + ); } @override From d3ba9143263a016a203ef77d0a69caf05ac11b2d Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Mon, 28 Jul 2025 13:37:42 -0700 Subject: [PATCH 8/8] - --- pkgs/example/lib/app/app.dart | 3 ++- .../lib/sdk/catalog/shared/genui_widget.dart | 14 ++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pkgs/example/lib/app/app.dart b/pkgs/example/lib/app/app.dart index aac8c41af..c017ae79d 100644 --- a/pkgs/example/lib/app/app.dart +++ b/pkgs/example/lib/app/app.dart @@ -69,7 +69,8 @@ class _MyHomePageState extends State<_MyHomePage> { ), body: Padding( padding: const EdgeInsets.all(16.0), - child: Center( + child: Align( + alignment: Alignment.topLeft, child: SingleChildScrollView( controller: _scrollController, child: GenUiWidget(_agent.controller), diff --git a/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart b/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart index 6b22abda2..209b34380 100644 --- a/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart +++ b/pkgs/example/lib/sdk/catalog/shared/genui_widget.dart @@ -21,6 +21,7 @@ class _GenUiWidgetState extends State { @override void initState() { + print('Initializing GenUiWidget'); super.initState(); _initialize(); } @@ -44,13 +45,7 @@ class _GenUiWidgetState extends State { return const Center(child: CircularProgressIndicator()); } - return Column( - children: [ - builder(context), - const SizedBox(height: 16.0), - _buildChatBox(), - ], - ); + return builder(context); } void _onInput(UserInput input) { @@ -59,5 +54,8 @@ class _GenUiWidgetState extends State { _initialize(); } - Widget _buildChatBox() => ChatBox(_onInput); + Widget _buildChatBox() { + print('Building chat box'); + return ChatBox(_onInput); + } }