Skip to content

Commit 76b68c8

Browse files
committed
Merge branch 'main' into firebaseai/bidi_new_inputs
2 parents 0ceb1e2 + bf84c6a commit 76b68c8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+948
-259
lines changed

packages/firebase_ai/firebase_ai/example/lib/main.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ class _HomeScreenState extends State<HomeScreen> {
167167
) {
168168
switch (index) {
169169
case 0:
170-
return ChatPage(title: 'Chat', model: currentModel);
170+
return ChatPage(
171+
title: 'Chat',
172+
useVertexBackend: useVertexBackend,
173+
);
171174
case 1:
172175
return AudioPage(title: 'Audio', model: currentModel);
173176
case 2:
@@ -199,7 +202,10 @@ class _HomeScreenState extends State<HomeScreen> {
199202

200203
default:
201204
// Fallback to the first page in case of an unexpected index
202-
return ChatPage(title: 'Chat', model: currentModel);
205+
return ChatPage(
206+
title: 'Chat',
207+
useVertexBackend: useVertexBackend,
208+
);
203209
}
204210
}
205211

packages/firebase_ai/firebase_ai/example/lib/pages/audio_page.dart

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class _AudioPageState extends State<AudioPage> {
9999
Future<void> _submitAudioToModel(audioPart) async {
100100
try {
101101
String textPrompt = 'What is in the audio recording?';
102-
final prompt = TextPart('What is in the audio recording?');
102+
const prompt = TextPart('What is in the audio recording?');
103103

104104
setState(() {
105105
_messages.add(MessageData(text: textPrompt, fromUser: true));
@@ -137,11 +137,6 @@ class _AudioPageState extends State<AudioPage> {
137137
itemBuilder: (context, idx) {
138138
return MessageWidget(
139139
text: _messages[idx].text,
140-
image: Image.memory(
141-
_messages[idx].imageBytes!,
142-
cacheWidth: 400,
143-
cacheHeight: 400,
144-
),
145140
isFromUser: _messages[idx].fromUser ?? false,
146141
);
147142
},

packages/firebase_ai/firebase_ai/example/lib/pages/chat_page.dart

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,58 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
import 'package:firebase_auth/firebase_auth.dart';
1516
import 'package:flutter/material.dart';
1617
import 'package:firebase_ai/firebase_ai.dart';
1718
import '../widgets/message_widget.dart';
1819

1920
class ChatPage extends StatefulWidget {
20-
const ChatPage({super.key, required this.title, required this.model});
21+
const ChatPage({
22+
super.key,
23+
required this.title,
24+
required this.useVertexBackend,
25+
});
2126

2227
final String title;
23-
final GenerativeModel model;
28+
final bool useVertexBackend;
2429

2530
@override
2631
State<ChatPage> createState() => _ChatPageState();
2732
}
2833

2934
class _ChatPageState extends State<ChatPage> {
3035
ChatSession? _chat;
36+
GenerativeModel? _model;
3137
final ScrollController _scrollController = ScrollController();
3238
final TextEditingController _textController = TextEditingController();
3339
final FocusNode _textFieldFocus = FocusNode();
3440
final List<MessageData> _messages = <MessageData>[];
3541
bool _loading = false;
42+
bool _enableThinking = false;
3643

3744
@override
3845
void initState() {
3946
super.initState();
40-
_chat = widget.model.startChat();
47+
_initializeChat();
48+
}
49+
50+
void _initializeChat() {
51+
final generationConfig = GenerationConfig(
52+
thinkingConfig:
53+
_enableThinking ? ThinkingConfig(includeThoughts: true) : null,
54+
);
55+
if (widget.useVertexBackend) {
56+
_model = FirebaseAI.vertexAI(auth: FirebaseAuth.instance).generativeModel(
57+
model: 'gemini-2.5-flash',
58+
generationConfig: generationConfig,
59+
);
60+
} else {
61+
_model = FirebaseAI.googleAI(auth: FirebaseAuth.instance).generativeModel(
62+
model: 'gemini-2.5-flash',
63+
generationConfig: generationConfig,
64+
);
65+
}
66+
_chat = _model?.startChat();
4167
}
4268

4369
void _scrollDown() {
@@ -64,18 +90,31 @@ class _ChatPageState extends State<ChatPage> {
6490
mainAxisAlignment: MainAxisAlignment.center,
6591
crossAxisAlignment: CrossAxisAlignment.start,
6692
children: [
93+
SwitchListTile(
94+
title: const Text('Enable Thinking'),
95+
value: _enableThinking,
96+
onChanged: (bool value) {
97+
setState(() {
98+
_enableThinking = value;
99+
_initializeChat();
100+
});
101+
},
102+
),
67103
Expanded(
68104
child: ListView.builder(
69105
controller: _scrollController,
70106
itemBuilder: (context, idx) {
107+
final message = _messages[idx];
71108
return MessageWidget(
72-
text: _messages[idx].text,
73-
image: Image.memory(
74-
_messages[idx].imageBytes!,
75-
cacheWidth: 400,
76-
cacheHeight: 400,
77-
),
78-
isFromUser: _messages[idx].fromUser ?? false,
109+
text: message.text,
110+
image: message.imageBytes != null
111+
? Image.memory(
112+
message.imageBytes!,
113+
cacheWidth: 400,
114+
cacheHeight: 400,
115+
)
116+
: null,
117+
isFromUser: message.fromUser ?? false,
79118
);
80119
},
81120
itemCount: _messages.length,
@@ -130,6 +169,11 @@ class _ChatPageState extends State<ChatPage> {
130169
var response = await _chat?.sendMessage(
131170
Content.text(message),
132171
);
172+
final thought = response?.thoughtSummary;
173+
if (thought != null) {
174+
_messages
175+
.add(MessageData(text: thought, fromUser: false, isThought: true));
176+
}
133177
var text = response?.text;
134178
_messages.add(MessageData(text: text, fromUser: false));
135179

packages/firebase_ai/firebase_ai/example/lib/pages/document.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class _DocumentPageState extends State<DocumentPage> {
4747
const _prompt =
4848
'Write me a summary in one sentence what this document is about.';
4949

50-
final prompt = TextPart(_prompt);
50+
const prompt = TextPart(_prompt);
5151

5252
setState(() {
5353
_messages.add(MessageData(text: _prompt, fromUser: true));

packages/firebase_ai/firebase_ai/example/lib/pages/function_calling_page.dart

Lines changed: 102 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,28 +39,39 @@ class Location {
3939
}
4040

4141
class _FunctionCallingPageState extends State<FunctionCallingPage> {
42-
late final GenerativeModel _functionCallModel;
42+
late GenerativeModel _functionCallModel;
4343
final List<MessageData> _messages = <MessageData>[];
4444
bool _loading = false;
45+
bool _enableThinking = false;
4546

4647
@override
4748
void initState() {
4849
super.initState();
50+
_initializeModel();
51+
}
52+
53+
void _initializeModel() {
54+
final generationConfig = GenerationConfig(
55+
thinkingConfig:
56+
_enableThinking ? ThinkingConfig(includeThoughts: true) : null,
57+
);
4958
if (widget.useVertexBackend) {
5059
var vertexAI = FirebaseAI.vertexAI(auth: FirebaseAuth.instance);
5160
_functionCallModel = vertexAI.generativeModel(
52-
model: 'gemini-2.0-flash',
61+
model: 'gemini-2.5-flash',
5362
tools: [
5463
Tool.functionDeclarations([fetchWeatherTool]),
5564
],
65+
generationConfig: generationConfig,
5666
);
5767
} else {
5868
var googleAI = FirebaseAI.googleAI(auth: FirebaseAuth.instance);
5969
_functionCallModel = googleAI.generativeModel(
60-
model: 'gemini-2.0-flash',
70+
model: 'gemini-2.5-flash',
6171
tools: [
6272
Tool.functionDeclarations([fetchWeatherTool]),
6373
],
74+
generationConfig: generationConfig,
6475
);
6576
}
6677
}
@@ -118,12 +129,24 @@ class _FunctionCallingPageState extends State<FunctionCallingPage> {
118129
mainAxisAlignment: MainAxisAlignment.center,
119130
crossAxisAlignment: CrossAxisAlignment.start,
120131
children: [
132+
SwitchListTile(
133+
title: const Text('Enable Thinking'),
134+
value: _enableThinking,
135+
onChanged: (bool value) {
136+
setState(() {
137+
_enableThinking = value;
138+
_initializeModel();
139+
});
140+
},
141+
),
121142
Expanded(
122143
child: ListView.builder(
123144
itemBuilder: (context, idx) {
145+
final message = _messages[idx];
124146
return MessageWidget(
125-
text: _messages[idx].text,
126-
isFromUser: _messages[idx].fromUser ?? false,
147+
text: message.text,
148+
isFromUser: message.fromUser ?? false,
149+
isThought: message.isThought,
127150
);
128151
},
129152
itemCount: _messages.length,
@@ -158,43 +181,87 @@ class _FunctionCallingPageState extends State<FunctionCallingPage> {
158181
Future<void> _testFunctionCalling() async {
159182
setState(() {
160183
_loading = true;
184+
_messages.clear();
161185
});
162-
final functionCallChat = _functionCallModel.startChat();
163-
const prompt = 'What is the weather like in Boston on 10/02 in year 2024?';
186+
try {
187+
final functionCallChat = _functionCallModel.startChat();
188+
const prompt =
189+
'What is the weather like in Boston on 10/02 in year 2024?';
164190

165-
// Send the message to the generative model.
166-
var response = await functionCallChat.sendMessage(
167-
Content.text(prompt),
168-
);
191+
_messages.add(MessageData(text: prompt, fromUser: true));
169192

170-
final functionCalls = response.functionCalls.toList();
171-
// When the model response with a function call, invoke the function.
172-
if (functionCalls.isNotEmpty) {
173-
final functionCall = functionCalls.first;
174-
if (functionCall.name == 'fetchWeather') {
175-
Map<String, dynamic> location =
176-
functionCall.args['location']! as Map<String, dynamic>;
177-
var date = functionCall.args['date']! as String;
178-
var city = location['city'] as String;
179-
var state = location['state'] as String;
180-
final functionResult = await fetchWeather(Location(city, state), date);
181-
// Send the response to the model so that it can use the result to
182-
// generate text for the user.
183-
response = await functionCallChat.sendMessage(
184-
Content.functionResponse(functionCall.name, functionResult),
185-
);
186-
} else {
187-
throw UnimplementedError(
188-
'Function not declared to the model: ${functionCall.name}',
189-
);
193+
// Send the message to the generative model.
194+
var response = await functionCallChat.sendMessage(
195+
Content.text(prompt),
196+
);
197+
198+
final thought = response.thoughtSummary;
199+
if (thought != null) {
200+
_messages
201+
.add(MessageData(text: thought, fromUser: false, isThought: true));
190202
}
191-
}
192-
// When the model responds with non-null text content, print it.
193-
if (response.text case final text?) {
194-
_messages.add(MessageData(text: text));
203+
204+
final functionCalls = response.functionCalls.toList();
205+
// When the model response with a function call, invoke the function.
206+
if (functionCalls.isNotEmpty) {
207+
final functionCall = functionCalls.first;
208+
if (functionCall.name == 'fetchWeather') {
209+
Map<String, dynamic> location =
210+
functionCall.args['location']! as Map<String, dynamic>;
211+
var date = functionCall.args['date']! as String;
212+
var city = location['city'] as String;
213+
var state = location['state'] as String;
214+
final functionResult =
215+
await fetchWeather(Location(city, state), date);
216+
// Send the response to the model so that it can use the result to
217+
// generate text for the user.
218+
response = await functionCallChat.sendMessage(
219+
Content.functionResponse(functionCall.name, functionResult),
220+
);
221+
} else {
222+
throw UnimplementedError(
223+
'Function not declared to the model: ${functionCall.name}',
224+
);
225+
}
226+
}
227+
// When the model responds with non-null text content, print it.
228+
if (response.text case final text?) {
229+
_messages.add(MessageData(text: text));
230+
setState(() {
231+
_loading = false;
232+
});
233+
}
234+
} catch (e) {
235+
_showError(e.toString());
236+
setState(() {
237+
_loading = false;
238+
});
239+
} finally {
195240
setState(() {
196241
_loading = false;
197242
});
198243
}
199244
}
245+
246+
void _showError(String message) {
247+
showDialog<void>(
248+
context: context,
249+
builder: (context) {
250+
return AlertDialog(
251+
title: const Text('Something went wrong'),
252+
content: SingleChildScrollView(
253+
child: SelectableText(message),
254+
),
255+
actions: [
256+
TextButton(
257+
onPressed: () {
258+
Navigator.of(context).pop();
259+
},
260+
child: const Text('OK'),
261+
),
262+
],
263+
);
264+
},
265+
);
266+
}
200267
}

packages/firebase_ai/firebase_ai/example/lib/pages/image_prompt_page.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ class _ImagePromptPageState extends State<ImagePromptPage> {
188188
final content = [
189189
Content.multi([
190190
TextPart(message),
191-
FileData(
191+
const FileData(
192192
'image/jpeg',
193193
'gs://vertex-ai-example-ef5a2.appspot.com/foodpic.jpg',
194194
),

packages/firebase_ai/firebase_ai/example/lib/pages/video_page.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ class _VideoPageState extends State<VideoPage> {
4646

4747
const _prompt = 'Can you tell me what is in the video?';
4848

49-
final prompt = TextPart(_prompt);
50-
5149
setState(() {
5250
_messages.add(MessageData(text: _prompt, fromUser: true));
5351
});
@@ -56,7 +54,7 @@ class _VideoPageState extends State<VideoPage> {
5654
InlineDataPart('video/mp4', videoBytes.buffer.asUint8List());
5755

5856
final response = await widget.model.generateContent([
59-
Content.multi([prompt, videoPart]),
57+
Content.multi([const TextPart(_prompt), videoPart]),
6058
]);
6159

6260
setState(() {

0 commit comments

Comments
 (0)