Repository navigation
docs(ai): say conversation builders read changing state through a Listenable (FLU-1107) #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -915,22 +915,38 @@ AIConversationView( | |||||
| The rows are `AIUserMessageView` and `AIAssistantMessageView`, with attachments as | ||||||
| `AIMessageAttachmentView`. Under each finished reply there is room for actions, empty until you | ||||||
| set `AIComponentBuilders.replyActions`, typically to an `AIReplyActionsView` (copy, share, | ||||||
| regenerate, rate): | ||||||
| regenerate, rate). | ||||||
|
|
||||||
| The conversation caches its rows: a row is built again only when what its message says changes, or | ||||||
| the builders do, so a streaming reply doesn't rebuild the rest. Two consequences for builders that | ||||||
| show state of your own, such as a reply's rating: | ||||||
|
|
||||||
| - Read that state through a `Listenable` (a `ListenableBuilder` or `ValueListenableBuilder` in what | ||||||
| the builder returns). A value captured directly isn't read again when it changes, so a tapped 👍 | ||||||
| wouldn't fill. | ||||||
| - Create the builders once, such as in a `late final` field, not in `build`: new builders rebuild | ||||||
| every row whenever your widget rebuilds, such as on every chunk of a streaming reply. | ||||||
|
|
||||||
| ```dart | ||||||
| AIComponentFactory( | ||||||
| builders: AIComponentBuilders( | ||||||
| replyActions: (context, props) => AIReplyActionsView( | ||||||
| // In your State: | ||||||
| final ratings = ValueNotifier<Map<String, AIMessageRating>>({}); | ||||||
|
|
||||||
| late final builders = AIComponentBuilders( | ||||||
| replyActions: (context, props) => ValueListenableBuilder( | ||||||
| valueListenable: ratings, | ||||||
| builder: (context, rated, _) => AIReplyActionsView( | ||||||
| text: props.text, | ||||||
| actions: AIReplyActions( | ||||||
| rating: ratings[props.message.id], | ||||||
| onRate: (rating) => rate(props.message, rating), | ||||||
| rating: rated[props.message.id], | ||||||
| onRate: (rating) => rate(props.message, rating), // updates `ratings` | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional: show how
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 3b7168e. |
||||||
| onShare: (text, origin) => SharePlus.instance.share(ShareParams(text: text, sharePositionOrigin: origin)), | ||||||
| ), | ||||||
| ), | ||||||
| ), | ||||||
| child: conversation, | ||||||
| ) | ||||||
| ); | ||||||
|
|
||||||
| // In build: | ||||||
| AIComponentFactory(builders: builders, child: conversation) | ||||||
| ``` | ||||||
|
|
||||||
| Attachments are `AIMessageAttachment`s. `AIMessageAttachmentView` shows images as thumbnails and | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -126,25 +126,42 @@ class AIComponentBuilders with Diagnosticable { | |||||
| /// A message the person sent, in [AIConversationView]. Defaults to | ||||||
| /// [AIUserMessageView]. | ||||||
| /// | ||||||
| /// The conversation builds a row again only when what [AIMessage] says about | ||||||
| /// it changes, so read anything else that changes, such as a send status, | ||||||
| /// from your own listenable rather than from the message. | ||||||
| /// The conversation caches its rows: it builds one again only when what | ||||||
| /// [AIMessage] says about it changes, or the builders do. Read anything else | ||||||
| /// that changes, such as a send status, through a [Listenable] of your own | ||||||
| /// (a [ListenableBuilder] or [ValueListenableBuilder] in what you return), | ||||||
| /// and create the builders once, not in `build`. | ||||||
| final AIComponentBuilder<AIUserMessageProps>? userMessage; | ||||||
|
|
||||||
| /// A reply of the agent, in [AIConversationView]. Defaults to | ||||||
| /// [AIAssistantMessageView]. Like [userMessage], it is built again only when | ||||||
| /// what [AIMessage] says about the reply changes. | ||||||
| /// what [AIMessage] says about the reply changes, or the builders do, so read | ||||||
| /// state of your own through a [Listenable]. | ||||||
| final AIComponentBuilder<AIAssistantMessageProps>? assistantMessage; | ||||||
|
|
||||||
| /// What the person can do with a finished reply, shown under it in | ||||||
| /// [AIConversationView] once its answer is typed out. Nothing by default; an | ||||||
| /// [AIReplyActionsView] is what to return: | ||||||
| /// [AIReplyActionsView] is what to return. | ||||||
| /// | ||||||
| /// Like [userMessage], it is built again only when the reply changes, or the | ||||||
| /// builders do. So read the rating, which changes without the reply | ||||||
| /// changing, through a [Listenable], and create the builders once: | ||||||
| /// | ||||||
| /// ```dart | ||||||
| /// replyActions: (context, props) => AIReplyActionsView( | ||||||
| /// text: props.text, | ||||||
| /// actions: AIReplyActions(onRegenerate: props.isLatestReply ? chat.regenerate : null), | ||||||
| /// ), | ||||||
| /// // In your State, next to a `ValueNotifier<Map<String, AIMessageRating>> ratings`. | ||||||
| /// late final builders = AIComponentBuilders( | ||||||
| /// replyActions: (context, props) => ValueListenableBuilder( | ||||||
| /// valueListenable: ratings, | ||||||
| /// builder: (context, rated, _) => AIReplyActionsView( | ||||||
| /// text: props.text, | ||||||
| /// actions: AIReplyActions( | ||||||
| /// rating: rated[props.message.id], | ||||||
| /// onRate: (rating) => rate(props.message, rating), // updates `ratings` | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional: same as in the README. Assigning a new map is what makes the
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 3b7168e. |
||||||
| /// onRegenerate: props.isLatestReply ? chat.regenerate : null, | ||||||
| /// ), | ||||||
| /// ), | ||||||
| /// ), | ||||||
| /// ); | ||||||
| /// ``` | ||||||
| final AIComponentBuilder<AIReplyActionsProps>? replyActions; | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -41,8 +41,13 @@ import 'package:stream_chat_flutter_ai/src/theme/components/conversation_theme.d | |||||
| /// The rows can be replaced through [AIComponentBuilders.userMessage] and | ||||||
| /// [AIComponentBuilders.assistantMessage], and under each finished reply | ||||||
| /// [AIComponentBuilders.replyActions] shows the person's actions, nothing until | ||||||
| /// you set it. A row is only built again when what it shows changes, so a | ||||||
| /// reply streaming in does not rebuild the rest of the conversation. | ||||||
| /// you set it. A row is only built again when what its message says changes, | ||||||
| /// or the builders do, so a reply streaming in does not rebuild the rest of the | ||||||
| /// conversation. A builder that shows state of your own, such as a reply's | ||||||
| /// rating, must therefore read it through a [Listenable] (a | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: "must" says more than the code does. The row clears its cache in
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 3b7168e. |
||||||
| /// [ListenableBuilder] or [ValueListenableBuilder] in what it returns), and | ||||||
| /// the builders should be created once rather than in `build`, since new ones | ||||||
| /// rebuild every row. | ||||||
| /// | ||||||
| /// Place the composer below the list rather than over it, so the list knows how | ||||||
| /// much of it is visible. | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional: the same applies to
onAttachmentTapandapprover._Showncompares both, so anonAttachmentTap: (a) => ...closure written inbuildrebuilds every row on each host rebuild, just like inline builders. I checked this with a test. TheAIConversationViewclass doc could say the same.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 3b7168e, in the README and the
AIConversationViewclass doc.