feat: background streaming of responses

This commit is contained in:
cogwheel0
2025-08-16 20:27:44 +05:30
parent 33fc26d755
commit 9be04ef2b9
23 changed files with 2676 additions and 322 deletions

View File

@@ -177,14 +177,25 @@ class _DocumentationMessageWidgetState
width: BorderWidth.regular,
),
),
child: Text(
widget.message.content,
style: TextStyle(
color: context.conduitTheme.chatBubbleUserText,
fontSize: AppTypography.bodyLarge,
height: 1.5,
letterSpacing: 0.1,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.message.content,
style: TextStyle(
color: context.conduitTheme.chatBubbleUserText,
fontSize: AppTypography.bodyMedium,
height: 1.5,
letterSpacing: 0.1,
),
),
// Action buttons for user messages
if (_showActions) ...[
const SizedBox(height: 12),
_buildUserActionButtons(),
],
],
),
),
),
@@ -391,13 +402,13 @@ class _DocumentationMessageWidgetState
if (match.start > lastIndex) {
final textSegment = content.substring(lastIndex, match.start);
widgets.add(
GptMarkdown(
textSegment,
style: TextStyle(
color: context.conduitTheme.textPrimary,
fontSize: AppTypography.bodyLarge,
height: 1.6,
letterSpacing: 0.1,
MediaQuery(
data: MediaQuery.of(context).copyWith(textScaler: const TextScaler.linear(1.0)),
child: GptMarkdown(
textSegment,
style: AppTypography.chatMessageStyle.copyWith(
color: context.conduitTheme.textPrimary,
),
),
),
);
@@ -414,13 +425,13 @@ class _DocumentationMessageWidgetState
if (lastIndex < content.length) {
final tail = content.substring(lastIndex);
widgets.add(
GptMarkdown(
tail,
style: TextStyle(
color: context.conduitTheme.textPrimary,
fontSize: AppTypography.bodyLarge,
height: 1.6,
letterSpacing: 0.1,
MediaQuery(
data: MediaQuery.of(context).copyWith(textScaler: const TextScaler.linear(1.0)),
child: GptMarkdown(
tail,
style: AppTypography.chatMessageStyle.copyWith(
color: context.conduitTheme.textPrimary,
),
),
),
);
@@ -611,6 +622,11 @@ class _DocumentationMessageWidgetState
}
Widget _buildActionButtons() {
final isErrorMessage = widget.message.content.contains('⚠️') ||
widget.message.content.contains('Error') ||
widget.message.content.contains('timeout') ||
widget.message.content.contains('retry options');
return Wrap(
spacing: 8,
runSpacing: 8,
@@ -622,25 +638,33 @@ class _DocumentationMessageWidgetState
label: 'Copy',
onTap: widget.onCopy,
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.hand_thumbsup
: Icons.thumb_up_outlined,
label: 'Like',
onTap: widget.onLike,
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.hand_thumbsdown
: Icons.thumb_down_outlined,
label: 'Dislike',
onTap: widget.onDislike,
),
_buildActionButton(
icon: Platform.isIOS ? CupertinoIcons.refresh : Icons.refresh,
label: 'Regenerate',
onTap: widget.onRegenerate,
),
if (isErrorMessage) ...[
_buildActionButton(
icon: Platform.isIOS ? CupertinoIcons.arrow_clockwise : Icons.refresh,
label: 'Retry',
onTap: widget.onRegenerate,
),
] else ...[
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.hand_thumbsup
: Icons.thumb_up_outlined,
label: 'Like',
onTap: widget.onLike,
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.hand_thumbsdown
: Icons.thumb_down_outlined,
label: 'Dislike',
onTap: widget.onDislike,
),
_buildActionButton(
icon: Platform.isIOS ? CupertinoIcons.refresh : Icons.refresh,
label: 'Regenerate',
onTap: widget.onRegenerate,
),
],
],
);
}
@@ -685,4 +709,25 @@ class _DocumentationMessageWidgetState
),
);
}
Widget _buildUserActionButtons() {
return Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildActionButton(
icon: Platform.isIOS ? CupertinoIcons.pencil : Icons.edit_outlined,
label: 'Edit',
onTap: widget.onEdit,
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.doc_on_clipboard
: Icons.content_copy,
label: 'Copy',
onTap: widget.onCopy,
),
],
);
}
}

View File

@@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../../../shared/theme/theme_extensions.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter/services.dart';
import 'dart:io' show Platform;
import 'dart:async';

View File

@@ -129,26 +129,32 @@ class _ModernMessageBubbleState extends ConsumerState<ModernMessageBubble>
),
boxShadow: ConduitShadows.high,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Display images if any
if (widget.message.attachmentIds != null &&
widget.message.attachmentIds!.isNotEmpty)
_buildAttachmentImages(),
// Display text content if any
if (widget.message.content.isNotEmpty) ...[
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Display images if any
if (widget.message.attachmentIds != null &&
widget.message.attachmentIds!.isNotEmpty)
const SizedBox(height: Spacing.sm),
_buildCustomText(
widget.message.content,
context.conduitTheme.chatBubbleUserText,
),
_buildAttachmentImages(),
// Display text content if any
if (widget.message.content.isNotEmpty) ...[
if (widget.message.attachmentIds != null &&
widget.message.attachmentIds!.isNotEmpty)
const SizedBox(height: Spacing.sm),
_buildCustomText(
widget.message.content,
context.conduitTheme.chatBubbleUserText,
),
],
// Action buttons for user messages
if (_showActions) ...[
const SizedBox(height: Spacing.md),
_buildUserActionButtons(),
],
],
],
),
),
),
),
),
@@ -701,15 +707,15 @@ class _ModernMessageBubbleState extends ConsumerState<ModernMessageBubble>
}
Widget _buildActionButtons() {
final isErrorMessage = widget.message.content.contains('⚠️') ||
widget.message.content.contains('Error') ||
widget.message.content.contains('timeout') ||
widget.message.content.contains('retry options');
return Wrap(
spacing: Spacing.sm,
runSpacing: Spacing.sm,
children: [
_buildActionButton(
icon: Platform.isIOS ? CupertinoIcons.pencil : Icons.edit_outlined,
label: 'Edit',
onTap: widget.onEdit,
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.doc_on_clipboard
@@ -717,32 +723,45 @@ class _ModernMessageBubbleState extends ConsumerState<ModernMessageBubble>
label: 'Copy',
onTap: widget.onCopy,
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.speaker_1
: Icons.volume_up_outlined,
label: 'Read',
onTap: () => _handleTextToSpeech(context),
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.hand_thumbsup
: Icons.thumb_up_outlined,
label: 'Like',
onTap: widget.onLike,
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.hand_thumbsdown
: Icons.thumb_down_outlined,
label: 'Dislike',
onTap: widget.onDislike,
),
_buildActionButton(
icon: Platform.isIOS ? CupertinoIcons.refresh : Icons.refresh,
label: 'Regenerate',
onTap: widget.onRegenerate,
),
if (isErrorMessage) ...[
_buildActionButton(
icon: Platform.isIOS ? CupertinoIcons.arrow_clockwise : Icons.refresh,
label: 'Retry',
onTap: widget.onRegenerate,
),
] else ...[
_buildActionButton(
icon: Platform.isIOS ? CupertinoIcons.pencil : Icons.edit_outlined,
label: 'Edit',
onTap: widget.onEdit,
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.speaker_1
: Icons.volume_up_outlined,
label: 'Read',
onTap: () => _handleTextToSpeech(context),
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.hand_thumbsup
: Icons.thumb_up_outlined,
label: 'Like',
onTap: widget.onLike,
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.hand_thumbsdown
: Icons.thumb_down_outlined,
label: 'Dislike',
onTap: widget.onDislike,
),
_buildActionButton(
icon: Platform.isIOS ? CupertinoIcons.refresh : Icons.refresh,
label: 'Regenerate',
onTap: widget.onRegenerate,
),
],
],
);
}
@@ -795,6 +814,34 @@ class _ModernMessageBubbleState extends ConsumerState<ModernMessageBubble>
);
}
Widget _buildUserActionButtons() {
return Wrap(
spacing: Spacing.sm,
runSpacing: Spacing.sm,
children: [
_buildActionButton(
icon: Platform.isIOS ? CupertinoIcons.pencil : Icons.edit_outlined,
label: 'Edit',
onTap: widget.onEdit,
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.doc_on_clipboard
: Icons.content_copy,
label: 'Copy',
onTap: widget.onCopy,
),
_buildActionButton(
icon: Platform.isIOS
? CupertinoIcons.speaker_1
: Icons.volume_up_outlined,
label: 'Read',
onTap: () => _handleTextToSpeech(context),
),
],
);
}
void _handleTextToSpeech(BuildContext context) {
// Implementation for text-to-speech functionality
ScaffoldMessenger.of(context).showSnackBar(