@@ -29,6 +29,10 @@ class Gemini extends StatefulWidget {
2929}
3030
3131class GeminiState extends State <Gemini > {
32+ // Set to `true` to show a "Thinking..." message immediately.
33+ // Set to `false` to wait for the first chunk before showing the message.
34+ final bool _isThinkingModel = true ;
35+
3236 final _uuid = const Uuid ();
3337 final _crossCache = CrossCache ();
3438 final _scrollController = ScrollController ();
@@ -285,7 +289,10 @@ class GeminiState extends State<Gemini> {
285289 final streamId = _uuid.v4 ();
286290 _currentStreamId = streamId;
287291 TextStreamMessage ? streamMessage;
288- var isFirstChunk = true ;
292+ Timer ? thinkingTimer;
293+
294+ // A flag to ensure the message is created only once.
295+ var messageInserted = false ;
289296
290297 // Store scroll state per stream ID
291298 _reachedTargetScroll[streamId] = false ;
@@ -295,6 +302,43 @@ class GeminiState extends State<Gemini> {
295302 _isStreaming = true ;
296303 });
297304
305+ // Helper to create and insert the message, ensuring it only happens once.
306+ Future <void > createAndInsertMessage () async {
307+ if (messageInserted || ! mounted) return ;
308+ messageInserted = true ;
309+
310+ // If the timer is still active, we beat it. Cancel it.
311+ thinkingTimer? .cancel ();
312+
313+ streamMessage = TextStreamMessage (
314+ id: streamId,
315+ authorId: _agent.id,
316+ createdAt: DateTime .now ().toUtc (),
317+ streamId: streamId,
318+ );
319+ await _chatController.insertMessage (streamMessage! );
320+ _streamManager.startStream (streamId, streamMessage! );
321+ }
322+
323+ if (_isThinkingModel) {
324+ // For thinking models, schedule the message insertion after a delay.
325+ thinkingTimer = Timer (const Duration (milliseconds: 300 ), () async {
326+ await createAndInsertMessage ();
327+ // When timer fires, message is inserted, scroll to the bottom.
328+ // This is needed because we use shouldScrollToEndWhenAtBottom: false,
329+ // due to custom scroll logic below, so we must also scroll to the
330+ // thinking label manually.
331+ WidgetsBinding .instance.addPostFrameCallback ((_) async {
332+ if (! _scrollController.hasClients || ! mounted) return ;
333+ await _scrollController.animateTo (
334+ _scrollController.position.maxScrollExtent,
335+ duration: const Duration (milliseconds: 250 ),
336+ curve: Curves .linearToEaseOut,
337+ );
338+ });
339+ });
340+ }
341+
298342 try {
299343 final response = _chatSession.sendMessageStream (content);
300344
@@ -305,18 +349,11 @@ class GeminiState extends State<Gemini> {
305349 final textChunk = chunk.text! ;
306350 if (textChunk.isEmpty) return ; // Skip empty chunks
307351
308- if (isFirstChunk) {
309- isFirstChunk = false ;
310-
311- // Create and insert the message ON the first chunk
312- streamMessage = TextStreamMessage (
313- id: streamId,
314- authorId: _agent.id,
315- createdAt: DateTime .now ().toUtc (),
316- streamId: streamId,
317- );
318- await _chatController.insertMessage (streamMessage! );
319- _streamManager.startStream (streamId, streamMessage! );
352+ // On the first valid chunk, ensure the message is inserted.
353+ // This handles both non-thinking models and thinking models where
354+ // the response arrives before the timer.
355+ if (! messageInserted) {
356+ await createAndInsertMessage ();
320357 }
321358
322359 // Ensure stream message exists before adding chunk
@@ -369,6 +406,7 @@ class GeminiState extends State<Gemini> {
369406 }
370407 },
371408 onDone: () async {
409+ thinkingTimer? .cancel ();
372410 // Stream completed successfully (only if message was created)
373411 if (streamMessage != null ) {
374412 await _streamManager.completeStream (streamId);
@@ -388,10 +426,12 @@ class GeminiState extends State<Gemini> {
388426 _reachedTargetScroll.remove (streamId);
389427 },
390428 onError: (error) async {
429+ thinkingTimer? .cancel ();
391430 _handleStreamError (streamId, error, streamMessage);
392431 },
393432 );
394433 } catch (error) {
434+ thinkingTimer? .cancel ();
395435 // Catch other potential errors during stream processing
396436 _handleStreamError (streamId, error, streamMessage);
397437 }
0 commit comments