Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/flutter_chat_core/lib/src/utils/typedefs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,13 @@ typedef ChatItem =
int? messageGroupingTimeoutInSeconds,
bool? isRemoved,
});

/// Builder signature for rendering streamed Markdown content.
/// Used by widgets like `FlyerChatTextStreamMessage` to customize Markdown rendering.
typedef GptMarkdownBuilder =
Widget Function(
BuildContext context,
String text,
TextStyle? paragraphStyle,
void Function(String url, String title)? onLinkTap,
);
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ class FlyerChatTextMessage extends StatelessWidget {
/// The callback function to handle link clicks.
final void Function(String url, String title)? onLinkTap;

/// Optional builder to customize how Markdown is rendered.
/// If provided, it will be used instead of the default [GptMarkdown] widget.
final GptMarkdownBuilder? gptMarkdownBuilder;

/// The position of the link preview widget relative to the text.
/// If set to [LinkPreviewPosition.none], the link preview widget will not be displayed.
/// A [LinkPreviewBuilder] must be provided for the preview to be displayed.
Expand Down Expand Up @@ -102,6 +106,7 @@ class FlyerChatTextMessage extends StatelessWidget {
this.timeAndStatusPosition = TimeAndStatusPosition.end,
this.timeAndStatusPositionInlineInsets = const EdgeInsets.only(bottom: 2),
this.onLinkTap,
this.gptMarkdownBuilder,
this.linkPreviewPosition = LinkPreviewPosition.bottom,
this.topWidget,
});
Expand Down Expand Up @@ -137,20 +142,31 @@ class FlyerChatTextMessage extends StatelessWidget {
)
: null;

final textContent = GptMarkdownTheme(
gptThemeData: GptMarkdownTheme.of(context).copyWith(
linkColor: isSentByMe ? sentLinksColor : receivedLinksColor,
linkHoverColor: isSentByMe ? sentLinksColor : receivedLinksColor,
),
child: GptMarkdown(
message.text,
style:
_isOnlyEmoji
? paragraphStyle?.copyWith(fontSize: onlyEmojiFontSize)
: paragraphStyle,
onLinkTap: onLinkTap,
),
);
final effectiveParagraphStyle =
_isOnlyEmoji
? paragraphStyle?.copyWith(fontSize: onlyEmojiFontSize)
: paragraphStyle;

final textContent =
gptMarkdownBuilder != null
? gptMarkdownBuilder!(
context,
message.text,
effectiveParagraphStyle,
onLinkTap,
)
: GptMarkdownTheme(
gptThemeData: GptMarkdownTheme.of(context).copyWith(
linkColor: isSentByMe ? sentLinksColor : receivedLinksColor,
linkHoverColor:
isSentByMe ? sentLinksColor : receivedLinksColor,
),
child: GptMarkdown(
message.text,
style: effectiveParagraphStyle,
onLinkTap: onLinkTap,
),
);

final linkPreviewWidget =
linkPreviewPosition != LinkPreviewPosition.none
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ class FlyerChatTextStreamMessage extends StatefulWidget {
/// The callback function to handle link clicks.
final void Function(String url, String title)? onLinkTap;

/// Optional builder to customize how Markdown is rendered.
/// If provided, it will be used instead of the default [GptMarkdown] widget.
final GptMarkdownBuilder? gptMarkdownBuilder;

/// The text to display while in the loading state. Defaults to "Thinking".
final String loadingText;

Expand Down Expand Up @@ -128,6 +132,7 @@ class FlyerChatTextStreamMessage extends StatefulWidget {
this.chunkAnimationDuration = const Duration(milliseconds: 350),
this.mode = TextStreamMessageMode.animatedOpacity,
this.onLinkTap,
this.gptMarkdownBuilder,
this.loadingText = 'Thinking',
this.shimmerBaseColor,
this.shimmerHighlightColor,
Expand Down Expand Up @@ -371,6 +376,15 @@ class _FlyerChatTextStreamMessageState extends State<FlyerChatTextStreamMessage>

if (widget.streamState is StreamStateCompleted) {
final state = widget.streamState as StreamStateCompleted;
final builder = widget.gptMarkdownBuilder;
if (builder != null) {
return builder(
context,
state.finalText,
paragraphStyle,
widget.onLinkTap,
);
}
return GptMarkdown(
state.finalText,
style: paragraphStyle,
Expand All @@ -392,6 +406,15 @@ class _FlyerChatTextStreamMessageState extends State<FlyerChatTextStreamMessage>

if (widget.mode == TextStreamMessageMode.instantMarkdown) {
final combinedText = _segments.map((s) => s.text).join('');
final builder = widget.gptMarkdownBuilder;
if (builder != null) {
return builder(
context,
combinedText,
paragraphStyle,
widget.onLinkTap,
);
}
return GptMarkdown(combinedText, style: paragraphStyle);
} else {
return RichText(
Expand Down