Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Gemini sample #2846

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
86 changes: 59 additions & 27 deletions pkgs/samples/lib/google_ai.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ class _ChatScreenState extends State<ChatScreen> {
body: switch (apiKey) {
final providedKey? => ChatWidget(apiKey: providedKey),
_ => ApiKeyWidget(onSubmitted: (key) {
setState(() => apiKey = key);
}),
setState(() => apiKey = key);
}),
},
);
}
Expand All @@ -75,8 +75,8 @@ class ApiKeyWidget extends StatelessWidget {
children: [
const Text(
'To use the Gemini API, you\'ll need an API key. '
'If you don\'t already have one, '
'create a key in Google AI Studio.',
'If you don\'t already have one, '
'create a key in Google AI Studio.',
),
const SizedBox(height: 8),
Link(
Expand All @@ -93,7 +93,7 @@ class ApiKeyWidget extends StatelessWidget {
Expanded(
child: TextField(
decoration:
textFieldDecoration(context, 'Enter your API key'),
textFieldDecoration(context, 'Enter your API key'),
controller: _textController,
onSubmitted: (value) {
onSubmitted(value);
Expand All @@ -117,9 +117,11 @@ class ApiKeyWidget extends StatelessWidget {
}

class ChatWidget extends StatefulWidget {
const ChatWidget({required this.apiKey, super.key});

final String apiKey;
const ChatWidget({
required this.apiKey,
super.key,
});

@override
State<ChatWidget> createState() => _ChatWidgetState();
Expand All @@ -130,7 +132,7 @@ class _ChatWidgetState extends State<ChatWidget> {
late final ChatSession _chat;
final ScrollController _scrollController = ScrollController();
final TextEditingController _textController = TextEditingController();
final FocusNode _textFieldFocus = FocusNode(debugLabel: 'TextField');
final FocusNode _textFieldFocus = FocusNode();
bool _loading = false;

@override
Expand All @@ -145,7 +147,7 @@ class _ChatWidgetState extends State<ChatWidget> {

void _scrollDown() {
WidgetsBinding.instance.addPostFrameCallback(
(_) => _scrollController.animateTo(
(_) => _scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(
milliseconds: 750,
Expand All @@ -157,19 +159,40 @@ class _ChatWidgetState extends State<ChatWidget> {

@override
Widget build(BuildContext context) {
final history = _chat.history.toList();
var textFieldDecoration = InputDecoration(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the same as the textFieldDecoration method you added below to share with the API screen? Do you want to keep sharing that implementation or nah?

contentPadding: const EdgeInsets.all(15),
hintText: 'Enter a prompt...',
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(14),
),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.secondary,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(14),
),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.secondary,
),
),
);

return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ListView.builder(
child: widget.apiKey.isNotEmpty
? ListView.builder(
controller: _scrollController,
itemBuilder: (context, idx) {
final content = history[idx];
final text = content.parts
var content = _chat.history.toList()[idx];
Copy link
Member

@parlough parlough Feb 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a note, I changed this piece intentionally to avoid recreating the same list, and potentially reevaluating the history iterable for each item, but I'll open an upstream fix so it's not lost.

Edit: google-gemini/generative-ai-dart#77

var text = content.parts
.whereType<TextPart>()
.map<String>((e) => e.text)
.join('');
Expand All @@ -178,7 +201,12 @@ class _ChatWidgetState extends State<ChatWidget> {
isFromUser: content.role == 'user',
);
},
itemCount: history.length,
itemCount: _chat.history.length,
)
: ListView(
children: const [
Text('No API key found. Please provide an API Key.'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good point to add this, since I think the login(?) page currently allows empty submissions.

We don't have a back button, so maybe we should say to restart the sample? However, it's hopefully a rare mistake, so it might be fine to not provide special details or handling to avoid diverging too much from the original sample.

],
),
),
Padding(
Expand All @@ -192,15 +220,16 @@ class _ChatWidgetState extends State<ChatWidget> {
child: TextField(
autofocus: true,
focusNode: _textFieldFocus,
decoration:
textFieldDecoration(context, 'Enter a prompt...'),
decoration: textFieldDecoration,
controller: _textController,
onSubmitted: (String value) {
_sendChatMessage(value);
},
),
),
const SizedBox.square(dimension: 15),
const SizedBox.square(
dimension: 15,
),
if (!_loading)
IconButton(
onPressed: () async {
Expand All @@ -227,13 +256,13 @@ class _ChatWidgetState extends State<ChatWidget> {
});

try {
final response = await _chat.sendMessage(
var response = await _chat.sendMessage(
Content.text(message),
);
final text = response.text;
var text = response.text;

if (text == null) {
_showError('Empty response.');
_showError('No response from API.');
return;
} else {
setState(() {
Expand Down Expand Up @@ -262,7 +291,7 @@ class _ChatWidgetState extends State<ChatWidget> {
return AlertDialog(
title: const Text('Something went wrong'),
content: SingleChildScrollView(
child: Text(message),
child: SelectableText(message),
),
actions: [
TextButton(
Expand All @@ -279,24 +308,24 @@ class _ChatWidgetState extends State<ChatWidget> {
}

class MessageWidget extends StatelessWidget {
final String text;
final bool isFromUser;

const MessageWidget({
super.key,
required this.text,
required this.isFromUser,
});

final String text;
final bool isFromUser;

@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment:
isFromUser ? MainAxisAlignment.end : MainAxisAlignment.start,
isFromUser ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
Flexible(
child: Container(
constraints: const BoxConstraints(maxWidth: 480),
constraints: const BoxConstraints(maxWidth: 600),
decoration: BoxDecoration(
color: isFromUser
? Theme.of(context).colorScheme.primaryContainer
Expand All @@ -308,7 +337,10 @@ class MessageWidget extends StatelessWidget {
horizontal: 20,
),
margin: const EdgeInsets.only(bottom: 8),
child: MarkdownBody(data: text),
child: MarkdownBody(
selectable: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woo, I was wanting this yesterday! Nice that the package makes it so easy :)

data: text,
),
),
),
],
Expand Down