From 8012484041b618d531f0a5ba6384af2fa3f7ffd5 Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Tue, 26 Aug 2025 12:08:02 -0700 Subject: [PATCH 1/4] image prompt page nano banana --- .../firebase_ai/example/lib/main.dart | 3 +- .../example/lib/pages/image_prompt_page.dart | 153 +++++++++--------- 2 files changed, 77 insertions(+), 79 deletions(-) diff --git a/packages/firebase_ai/firebase_ai/example/lib/main.dart b/packages/firebase_ai/firebase_ai/example/lib/main.dart index db1344210deb..67165d2fc7b6 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/main.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/main.dart @@ -182,7 +182,8 @@ class _HomeScreenState extends State { useVertexBackend: useVertexBackend, ); case 4: - return ImagePromptPage(title: 'Image Prompt', model: currentModel); + return ImagePromptPage( + title: 'Image Prompt', useVertexBackend: useVertexBackend); case 5: return ImagenPage(title: 'Imagen Model', model: currentImagenModel); case 6: diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart index 48fc8667af59..4018b60c1a74 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart @@ -17,22 +17,57 @@ import 'package:firebase_ai/firebase_ai.dart'; import 'package:flutter/services.dart'; import '../widgets/message_widget.dart'; +import 'dart:typed_data'; + +import 'package:image_picker/image_picker.dart'; + class ImagePromptPage extends StatefulWidget { - const ImagePromptPage({super.key, required this.title, required this.model}); + const ImagePromptPage( + {super.key, required this.title, required this.useVertexBackend}); final String title; - final GenerativeModel model; + + final bool useVertexBackend; @override State createState() => _ImagePromptPageState(); } class _ImagePromptPageState extends State { + final ImagePicker _picker = ImagePicker(); final ScrollController _scrollController = ScrollController(); final TextEditingController _textController = TextEditingController(); final FocusNode _textFieldFocus = FocusNode(); final List _generatedContent = []; bool _loading = false; + late final GenerativeModel _model; + XFile? _image; + + @override + void initState() { + super.initState(); + if (widget.useVertexBackend) { + _model = FirebaseAI.vertexAI(location: 'global').generativeModel( + model: 'gemini-2.5-flash-image-preview', + generationConfig: GenerationConfig( + responseModalities: [ + ResponseModalities.text, + ResponseModalities.image, + ], + ), + ); + } else { + _model = FirebaseAI.googleAI().generativeModel( + model: 'gemini-2.5-flash-image-preview', + generationConfig: GenerationConfig( + responseModalities: [ + ResponseModalities.text, + ResponseModalities.image, + ], + ), + ); + } + } void _scrollDown() { WidgetsBinding.instance.addPostFrameCallback( @@ -65,11 +100,13 @@ class _ImagePromptPageState extends State { var content = _generatedContent[idx]; return MessageWidget( text: content.text, - image: Image.memory( - content.imageBytes!, - cacheWidth: 400, - cacheHeight: 400, - ), + image: content.imageBytes != null + ? Image.memory( + content.imageBytes!, + cacheWidth: 400, + cacheHeight: 400, + ) + : null, isFromUser: content.fromUser ?? false, ); }, @@ -88,28 +125,28 @@ class _ImagePromptPageState extends State { autofocus: true, focusNode: _textFieldFocus, controller: _textController, + onSubmitted: _sendChatMessage, ), ), const SizedBox.square( dimension: 15, ), - if (!_loading) - IconButton( - onPressed: () async { - await _sendImagePrompt(_textController.text); - }, - icon: Icon( - Icons.image, - color: Theme.of(context).colorScheme.primary, - ), + IconButton( + onPressed: () async { + await _pickImage(); + }, + icon: Icon( + Icons.image, + color: Theme.of(context).colorScheme.primary, ), + ), if (!_loading) IconButton( onPressed: () async { - await _sendStorageUriPrompt(_textController.text); + await _sendChatMessage(_textController.text); }, icon: Icon( - Icons.storage, + Icons.send, color: Theme.of(context).colorScheme.primary, ), ) @@ -124,81 +161,40 @@ class _ImagePromptPageState extends State { ); } - Future _sendImagePrompt(String message) async { + Future _pickImage() async { + final image = await _picker.pickImage(source: ImageSource.gallery); + setState(() { + _image = image; + }); + } + + Future _sendChatMessage(String message) async { setState(() { _loading = true; }); + try { - ByteData catBytes = await rootBundle.load('assets/images/cat.jpg'); - ByteData sconeBytes = await rootBundle.load('assets/images/scones.jpg'); - final content = [ - Content.multi([ - TextPart(message), - // The only accepted mime types are image/*. - InlineDataPart('image/jpeg', catBytes.buffer.asUint8List()), - InlineDataPart('image/jpeg', sconeBytes.buffer.asUint8List()), - ]), - ]; + final imageBytes = await _image?.readAsBytes(); _generatedContent.add( MessageData( - imageBytes: catBytes.buffer.asUint8List(), + imageBytes: imageBytes, text: message, fromUser: true, ), ); - _generatedContent.add( - MessageData( - imageBytes: sconeBytes.buffer.asUint8List(), - fromUser: true, - ), - ); - var response = await widget.model.generateContent(content); - var text = response.text; - _generatedContent.add(MessageData(text: text, fromUser: false)); - - if (text == null) { - _showError('No response from API.'); - return; - } else { - setState(() { - _loading = false; - _scrollDown(); - }); - } - } catch (e) { - _showError(e.toString()); - setState(() { - _loading = false; - }); - } finally { - _textController.clear(); - setState(() { - _loading = false; - }); - _textFieldFocus.requestFocus(); - } - } - - Future _sendStorageUriPrompt(String message) async { - setState(() { - _loading = true; - }); - try { - final content = [ + var response = await _model.generateContent([ Content.multi([ TextPart(message), - const FileData( - 'image/jpeg', - 'gs://vertex-ai-example-ef5a2.appspot.com/foodpic.jpg', - ), + if (imageBytes != null) + // The only accepted mime types are image/*. + InlineDataPart('image/jpeg', imageBytes), ]), - ]; - _generatedContent.add(MessageData(text: message, fromUser: true)); - - var response = await widget.model.generateContent(content); + ]); var text = response.text; - _generatedContent.add(MessageData(text: text, fromUser: false)); + var image = response.inlineDataParts?.first?.bytes; + _generatedContent + .add(MessageData(text: text, imageBytes: image, fromUser: false)); if (text == null) { _showError('No response from API.'); @@ -218,6 +214,7 @@ class _ImagePromptPageState extends State { _textController.clear(); setState(() { _loading = false; + _image = null; }); _textFieldFocus.requestFocus(); } From c087ea5a4fc3fddb516e779b29d76901a83855d5 Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Tue, 26 Aug 2025 12:11:53 -0700 Subject: [PATCH 2/4] making sure vertex instance will differentiate with location --- packages/firebase_ai/firebase_ai/lib/src/firebase_ai.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firebase_ai/firebase_ai/lib/src/firebase_ai.dart b/packages/firebase_ai/firebase_ai/lib/src/firebase_ai.dart index d209d74161c4..7f3df0d1a3ef 100644 --- a/packages/firebase_ai/firebase_ai/lib/src/firebase_ai.dart +++ b/packages/firebase_ai/firebase_ai/lib/src/firebase_ai.dart @@ -68,7 +68,7 @@ class FirebaseAI extends FirebasePluginPlatform { bool? useLimitedUseAppCheckTokens, }) { app ??= Firebase.app(); - var instanceKey = '${app.name}::vertexai'; + var instanceKey = '${app.name}::vertexai::$location'; if (_cachedInstances.containsKey(instanceKey)) { return _cachedInstances[instanceKey]!; From cd35523a42beca12216f7418562f05e5e62d3f78 Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Tue, 26 Aug 2025 16:01:49 -0700 Subject: [PATCH 3/4] only show error when no response for both text and image --- .../firebase_ai/example/lib/pages/image_prompt_page.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart index 4018b60c1a74..0c5b797ceb1e 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart @@ -14,11 +14,8 @@ import 'package:flutter/material.dart'; import 'package:firebase_ai/firebase_ai.dart'; -import 'package:flutter/services.dart'; import '../widgets/message_widget.dart'; -import 'dart:typed_data'; - import 'package:image_picker/image_picker.dart'; class ImagePromptPage extends StatefulWidget { @@ -196,7 +193,7 @@ class _ImagePromptPageState extends State { _generatedContent .add(MessageData(text: text, imageBytes: image, fromUser: false)); - if (text == null) { + if (text == null && image == null) { _showError('No response from API.'); return; } else { From 44a0e77359f4b0bbe5429f72f64cf8880ce64598 Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Tue, 26 Aug 2025 21:23:23 -0700 Subject: [PATCH 4/4] googleAI don't have to set responsemodality --- .../firebase_ai/example/lib/pages/image_prompt_page.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart index 0c5b797ceb1e..51973c1d301e 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart @@ -56,12 +56,6 @@ class _ImagePromptPageState extends State { } else { _model = FirebaseAI.googleAI().generativeModel( model: 'gemini-2.5-flash-image-preview', - generationConfig: GenerationConfig( - responseModalities: [ - ResponseModalities.text, - ResponseModalities.image, - ], - ), ); } }