From 93e5dfe90614dc2e064c15ecff50717421d857e6 Mon Sep 17 00:00:00 2001 From: adar2378 Date: Sat, 9 Aug 2025 02:21:31 +0600 Subject: [PATCH] feat(core,flyer_chat_text_message,flyer_chat_text_stream_message): introduce GptMarkdownBuilder and wire into message widgets - core (flutter_chat_core): - add typedef `GptMarkdownBuilder` in [src/utils/typedefs.dart](cci:7://file:///Users/saifulislam/Development/fleek/flutter_chat_ui/packages/flutter_chat_ui/lib/src/utils/typedefs.dart:0:0-0:0) - flyer_chat_text_message: - add optional `gptMarkdownBuilder` field/param - use builder to render Markdown when provided; fallback to `GptMarkdownTheme` + [GptMarkdown](cci:1://file:///Users/saifulislam/Development/fleek/flutter_chat_ui/packages/flyer_chat_text_stream_message/lib/src/flyer_chat_text_stream_message.dart:19:6-23:7) - flyer_chat_text_stream_message: - add optional `gptMarkdownBuilder` field/param - use builder in completed state and in `instantMarkdown` mode; fallback to [GptMarkdown](cci:1://file:///Users/saifulislam/Development/fleek/flutter_chat_ui/packages/flyer_chat_text_stream_message/lib/src/flyer_chat_text_stream_message.dart:19:6-23:7) Why: - Standardize Markdown customization via shared typedef - Allow consumers to fully override Markdown rendering consistently Affects: - packages/flutter_chat_core - packages/flyer_chat_text_message - packages/flyer_chat_text_stream_message BREAKING CHANGE: none --- .../lib/src/utils/typedefs.dart | 10 +++++ .../lib/src/flyer_chat_text_message.dart | 44 +++++++++++++------ .../src/flyer_chat_text_stream_message.dart | 23 ++++++++++ 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/packages/flutter_chat_core/lib/src/utils/typedefs.dart b/packages/flutter_chat_core/lib/src/utils/typedefs.dart index aa8b4c6c6..9d44140af 100644 --- a/packages/flutter_chat_core/lib/src/utils/typedefs.dart +++ b/packages/flutter_chat_core/lib/src/utils/typedefs.dart @@ -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, + ); diff --git a/packages/flyer_chat_text_message/lib/src/flyer_chat_text_message.dart b/packages/flyer_chat_text_message/lib/src/flyer_chat_text_message.dart index 9b3d417e1..cecc8a067 100644 --- a/packages/flyer_chat_text_message/lib/src/flyer_chat_text_message.dart +++ b/packages/flyer_chat_text_message/lib/src/flyer_chat_text_message.dart @@ -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. @@ -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, }); @@ -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 diff --git a/packages/flyer_chat_text_stream_message/lib/src/flyer_chat_text_stream_message.dart b/packages/flyer_chat_text_stream_message/lib/src/flyer_chat_text_stream_message.dart index deab4d099..26fc53510 100644 --- a/packages/flyer_chat_text_stream_message/lib/src/flyer_chat_text_stream_message.dart +++ b/packages/flyer_chat_text_stream_message/lib/src/flyer_chat_text_stream_message.dart @@ -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; @@ -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, @@ -371,6 +376,15 @@ class _FlyerChatTextStreamMessageState extends State 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, @@ -392,6 +406,15 @@ class _FlyerChatTextStreamMessageState extends State 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(