diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index edcf9af3..0b886265 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -49,9 +49,9 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- CADisableMinimumFrameDurationOnPhone
-
- UIApplicationSupportsIndirectInputEvents
-
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
diff --git a/example/lib/custom_strings/custom_strings.dart b/example/lib/custom_strings/custom_strings.dart
new file mode 100644
index 00000000..da1749a8
--- /dev/null
+++ b/example/lib/custom_strings/custom_strings.dart
@@ -0,0 +1,102 @@
+// Copyright 2025 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:firebase_ai/firebase_ai.dart';
+import 'package:firebase_core/firebase_core.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
+
+import '../firebase_options.dart';
+
+void main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+ await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
+ runApp(const App());
+}
+
+class App extends StatelessWidget {
+ static const title = 'Example: Google Gemini AI';
+
+ const App({super.key});
+
+ @override
+ Widget build(BuildContext context) =>
+ const MaterialApp(title: title, home: CustomStringsExample());
+}
+
+class CustomStringsExample extends StatelessWidget {
+ static const title = 'Custom Chat Strings';
+
+ const CustomStringsExample({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final customStrings = const LlmChatViewStrings(
+ addAttachment: 'Add',
+ attachFile: 'Attach File',
+ takePhoto: 'Take Photo',
+ stop: 'âšī¸ Stop',
+ close: 'â Close',
+ cancel: 'â Cancel',
+ copyToClipboard: 'đ Copy',
+ editMessage: 'âī¸ Edit',
+ attachImage: 'đŧī¸ Add Image',
+ recordAudio: 'đ¤ Record',
+ submitMessage: 'đ¤ Send',
+ closeMenu: 'â Close Menu',
+
+ // Message related
+ typeAMessage: 'Type your message here...',
+ recording: 'đ´ Recording...',
+ tapToStop: 'Tap to stop',
+ tapToRecord: 'Tap to record',
+ releaseToCancel: 'Release to cancel',
+ slideToCancel: 'Slide to cancel',
+
+ submit: 'Submit',
+ send: 'Send',
+ delete: 'đī¸ Delete',
+ edit: 'âī¸ Edit',
+ copy: 'đ Copy',
+ share: 'âī¸ Share',
+ retry: 'đ Retry',
+ yes: 'â
Yes',
+ no: 'â No',
+ clear: 'đī¸ Clear',
+ search: 'đ Search',
+
+ // Messages and errors
+ messageCopiedToClipboard: 'đ Copied to clipboard!',
+ editing: 'âī¸ Editing',
+ error: 'â Error',
+ cancelMessage: 'Cancel',
+ confirmDelete: 'Confirm Delete',
+ areYouSureYouWantToDeleteThisMessage:
+ 'Are you sure you want to delete this message?',
+ errorSendingMessage: 'â Failed to send message',
+ errorLoadingMessages: 'â Failed to load messages',
+ noMessagesYet: 'No messages yet. Start the conversation!',
+ tapToRetry: 'Tap to retry',
+ noResultsFound: 'No results found',
+ unableToRecordAudio: 'Unable to record audio',
+ unsupportedImageSource: 'Unsupported image source',
+ unableToPickImage: 'Unable to pick image',
+ unableToPickFile: 'Unable to pick file',
+ unableToPickUrl: 'Unable to process URL',
+ );
+
+ return Scaffold(
+ appBar: AppBar(title: const Text(App.title)),
+ body: LlmChatView(
+ provider: FirebaseProvider(
+ model: FirebaseAI.googleAI().generativeModel(
+ model: 'gemini-2.0-flash',
+ ),
+ ),
+ strings: customStrings,
+ style: LlmChatViewStyle(strings: customStrings),
+ ),
+ );
+ }
+}
diff --git a/lib/flutter_ai_toolkit.dart b/lib/flutter_ai_toolkit.dart
index 96ad500d..de468e07 100644
--- a/lib/flutter_ai_toolkit.dart
+++ b/lib/flutter_ai_toolkit.dart
@@ -19,3 +19,4 @@ export 'src/providers/interface/chat_message.dart';
export 'src/providers/providers.dart';
export 'src/styles/styles.dart';
export 'src/views/llm_chat_view/llm_chat_view.dart';
+export 'src/strings/strings.dart';
diff --git a/lib/src/chat_view_model/chat_view_model.dart b/lib/src/chat_view_model/chat_view_model.dart
index e44e089f..c27663f5 100644
--- a/lib/src/chat_view_model/chat_view_model.dart
+++ b/lib/src/chat_view_model/chat_view_model.dart
@@ -5,6 +5,7 @@
import 'package:flutter/foundation.dart';
import '../providers/interface/llm_provider.dart';
+import '../strings/llm_chat_view_strings.dart';
import '../styles/llm_chat_view_style.dart';
import '../views/response_builder.dart';
@@ -35,6 +36,7 @@ class ChatViewModel {
required this.speechToText,
required this.enableAttachments,
required this.enableVoiceNotes,
+ this.strings = const LlmChatViewStrings(),
});
/// The LLM provider for the chat interface.
@@ -92,6 +94,12 @@ class ChatViewModel {
/// will be disabled.
final bool enableVoiceNotes;
+ /// The strings used throughout the chat interface.
+ ///
+ /// This provides access to all the text strings used in the chat interface,
+ /// allowing for easy customization and internationalization.
+ final LlmChatViewStrings strings;
+
// The following is needed to support the
// ChatViewModelProvider.updateShouldNotify implementation
@override
@@ -104,8 +112,10 @@ class ChatViewModel {
other.welcomeMessage == welcomeMessage &&
other.responseBuilder == responseBuilder &&
other.messageSender == messageSender &&
+ other.speechToText == speechToText &&
other.enableAttachments == enableAttachments &&
- other.enableVoiceNotes == enableVoiceNotes);
+ other.enableVoiceNotes == enableVoiceNotes &&
+ other.strings == strings);
// the following is best practices when overriding operator ==
@override
@@ -116,7 +126,9 @@ class ChatViewModel {
welcomeMessage,
responseBuilder,
messageSender,
+ speechToText,
enableAttachments,
enableVoiceNotes,
+ strings,
);
}
diff --git a/lib/src/strings/llm_chat_view_strings.dart b/lib/src/strings/llm_chat_view_strings.dart
new file mode 100644
index 00000000..0bc100cc
--- /dev/null
+++ b/lib/src/strings/llm_chat_view_strings.dart
@@ -0,0 +1,358 @@
+// Copyright 2024 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/foundation.dart';
+
+/// A class that contains all the strings used in the LlmChatView.
+///
+/// This class provides a way to customize the text displayed in the chat interface.
+/// You can use the default values or provide your own custom strings.
+@immutable
+class LlmChatViewStrings {
+ /// Default instance with all default values
+ static const LlmChatViewStrings defaults = LlmChatViewStrings();
+
+ /// Input/Attachment related strings
+ /// Text for the add attachment button.
+ final String addAttachment;
+
+ /// Label for attaching a file.
+ final String attachFile;
+
+ /// Label for taking a photo.
+ final String takePhoto;
+
+ /// Text for the stop button.
+ final String stop;
+
+ /// Text for the close button.
+ final String close;
+
+ /// Text for the cancel button.
+ final String cancel;
+
+ /// Text for the copy to clipboard action.
+ final String copyToClipboard;
+
+ /// Text for the edit message action.
+ final String editMessage;
+
+ /// Label for attaching an image.
+ final String attachImage;
+
+ /// Text for the record audio button.
+ final String recordAudio;
+
+ /// Text for the submit message button.
+ final String submitMessage;
+
+ /// Text for closing a menu.
+ final String closeMenu;
+
+ /// Error message when unable to record audio.
+ final String unableToRecordAudio;
+
+ /// Error message prefix for unsupported image sources.
+ final String unsupportedImageSource;
+
+ /// Error message prefix when unable to pick an image.
+ final String unableToPickImage;
+
+ /// Error message prefix when unable to pick a file.
+ final String unableToPickFile;
+
+ /// Error message prefix when unable to pick a url.
+ final String unableToPickUrl;
+
+ /// Confirmation message when a message is copied to clipboard.
+ final String messageCopiedToClipboard;
+
+ /// Label indicating editing mode.
+ final String editing;
+
+ /// Generic error message.
+ final String error;
+
+ /// Text for cancel action in dialogs.
+ final String cancelMessage;
+
+ /// Text for submit action.
+ final String submit;
+
+ /// Text for the send button.
+ final String send;
+
+ /// Placeholder text for the message input field.
+ final String typeAMessage;
+
+ /// Label shown during audio recording.
+ final String recording;
+
+ /// Instruction to stop recording audio.
+ final String tapToStop;
+
+ /// Instruction to start recording audio.
+ final String tapToRecord;
+
+ /// Instruction shown when dragging to cancel recording.
+ final String releaseToCancel;
+
+ /// Instruction shown when sliding to cancel an action.
+ final String slideToCancel;
+
+ /// Text for the delete action.
+ final String delete;
+
+ /// Title for the delete confirmation dialog.
+ final String confirmDelete;
+
+ /// Confirmation message for message deletion.
+ final String areYouSureYouWantToDeleteThisMessage;
+
+ /// Affirmative response text (e.g., 'YES', 'OK').
+ final String yes;
+
+ /// Negative response text (e.g., 'NO', 'Cancel').
+ final String no;
+
+ /// Text for the edit action.
+ final String edit;
+
+ /// Text for the copy action.
+ final String copy;
+
+ /// Text for the share action.
+ final String share;
+
+ /// Text for the retry action.
+ final String retry;
+
+ /// Error message when failing to send a message.
+ final String errorSendingMessage;
+
+ /// Error message when failing to load messages.
+ final String errorLoadingMessages;
+
+ /// Placeholder text when there are no messages.
+ final String noMessagesYet;
+
+ /// Instruction to retry a failed action.
+ final String tapToRetry;
+
+ /// Label for the search functionality.
+ final String search;
+
+ /// Text for clearing input or search.
+ final String clear;
+
+ /// Message shown when no search results are found.
+ final String noResultsFound;
+
+ /// Creates a new instance of [LlmChatViewStrings] with the given strings.
+ ///
+ /// All parameters are optional and will default to the provided values.
+ const LlmChatViewStrings({
+ // Input/Attachment related
+ this.addAttachment = 'Add Attachment',
+ this.attachFile = 'Attach File',
+ this.takePhoto = 'Take Photo',
+ this.attachImage = 'Attach Image',
+ this.recordAudio = 'Record Audio',
+ this.typeAMessage = 'Type a message...',
+ this.recording = 'Recording...',
+ this.tapToStop = 'Tap to stop',
+ this.tapToRecord = 'Tap to record',
+ this.releaseToCancel = 'Release to cancel',
+ this.slideToCancel = 'Slide to cancel',
+
+ // Common actions
+ this.stop = 'Stop',
+ this.close = 'Close',
+ this.cancel = 'Cancel',
+ this.submit = 'Submit',
+ this.send = 'Send',
+ this.delete = 'Delete',
+ this.edit = 'Edit',
+ this.copy = 'Copy',
+ this.share = 'Share',
+ this.retry = 'Retry',
+ this.yes = 'Yes',
+ this.no = 'No',
+ this.clear = 'Clear',
+ this.search = 'Search',
+
+ // Messages and errors
+ this.copyToClipboard = 'Copy to Clipboard',
+ this.editMessage = 'Edit Message',
+ this.submitMessage = 'Submit Message',
+ this.closeMenu = 'Close Menu',
+ this.unableToRecordAudio = 'Unable to record audio',
+ this.unsupportedImageSource = 'Unsupported image source: ',
+ this.unableToPickImage = 'Unable to pick an image: ',
+ this.unableToPickFile = 'Unable to pick a file: ',
+ this.unableToPickUrl = 'Unable to pick a URL: ',
+ this.messageCopiedToClipboard = 'Message copied to clipboard',
+ this.editing = 'Editing',
+ this.error = 'Error',
+ this.cancelMessage = 'Cancel',
+ this.confirmDelete = 'Confirm Delete',
+ this.areYouSureYouWantToDeleteThisMessage =
+ 'Are you sure you want to delete this message?',
+ this.errorSendingMessage = 'Error sending message',
+ this.errorLoadingMessages = 'Error loading messages',
+ this.noMessagesYet = 'No messages yet',
+ this.tapToRetry = 'Tap to retry',
+ this.noResultsFound = 'No results found',
+ });
+
+ /// Creates a copy of this [LlmChatViewStrings] with the given fields replaced
+ /// with the new values.
+ LlmChatViewStrings copyWith({
+ String? addAttachment,
+ String? attachFile,
+ String? takePhoto,
+ String? stop,
+ String? close,
+ String? cancel,
+ String? copyToClipboard,
+ String? editMessage,
+ String? attachImage,
+ String? recordAudio,
+ String? submitMessage,
+ String? closeMenu,
+ String? unableToRecordAudio,
+ String? unsupportedImageSource,
+ String? unableToPickImage,
+ String? unableToPickFile,
+ String? unableToPickUrl,
+ String? messageCopiedToClipboard,
+ String? editing,
+ String? error,
+ String? cancelMessage,
+ String? submit,
+ String? send,
+ String? typeAMessage,
+ String? recording,
+ String? tapToStop,
+ String? tapToRecord,
+ String? releaseToCancel,
+ String? slideToCancel,
+ String? delete,
+ String? confirmDelete,
+ String? areYouSureYouWantToDeleteThisMessage,
+ String? yes,
+ String? no,
+ String? edit,
+ String? copy,
+ String? share,
+ String? retry,
+ String? errorSendingMessage,
+ String? errorLoadingMessages,
+ String? noMessagesYet,
+ String? tapToRetry,
+ String? search,
+ String? clear,
+ String? noResultsFound,
+ String? today,
+ String? yesterday,
+ String? lastWeek,
+ String? older,
+ }) {
+ return LlmChatViewStrings(
+ addAttachment: addAttachment ?? this.addAttachment,
+ attachFile: attachFile ?? this.attachFile,
+ takePhoto: takePhoto ?? this.takePhoto,
+ stop: stop ?? this.stop,
+ close: close ?? this.close,
+ cancel: cancel ?? this.cancel,
+ copyToClipboard: copyToClipboard ?? this.copyToClipboard,
+ editMessage: editMessage ?? this.editMessage,
+ attachImage: attachImage ?? this.attachImage,
+ recordAudio: recordAudio ?? this.recordAudio,
+ submitMessage: submitMessage ?? this.submitMessage,
+ closeMenu: closeMenu ?? this.closeMenu,
+ unableToRecordAudio: unableToRecordAudio ?? this.unableToRecordAudio,
+ unsupportedImageSource:
+ unsupportedImageSource ?? this.unsupportedImageSource,
+ unableToPickImage: unableToPickImage ?? this.unableToPickImage,
+ unableToPickFile: unableToPickFile ?? this.unableToPickFile,
+ unableToPickUrl: unableToPickUrl ?? this.unableToPickUrl,
+ messageCopiedToClipboard:
+ messageCopiedToClipboard ?? this.messageCopiedToClipboard,
+ editing: editing ?? this.editing,
+ error: error ?? this.error,
+ cancelMessage: cancelMessage ?? this.cancelMessage,
+ submit: submit ?? this.submit,
+ send: send ?? this.send,
+ typeAMessage: typeAMessage ?? this.typeAMessage,
+ recording: recording ?? this.recording,
+ tapToStop: tapToStop ?? this.tapToStop,
+ tapToRecord: tapToRecord ?? this.tapToRecord,
+ releaseToCancel: releaseToCancel ?? this.releaseToCancel,
+ slideToCancel: slideToCancel ?? this.slideToCancel,
+ delete: delete ?? this.delete,
+ confirmDelete: confirmDelete ?? this.confirmDelete,
+ areYouSureYouWantToDeleteThisMessage:
+ areYouSureYouWantToDeleteThisMessage ??
+ this.areYouSureYouWantToDeleteThisMessage,
+ yes: yes ?? this.yes,
+ no: no ?? this.no,
+ edit: edit ?? this.edit,
+ copy: copy ?? this.copy,
+ share: share ?? this.share,
+ retry: retry ?? this.retry,
+ errorSendingMessage: errorSendingMessage ?? this.errorSendingMessage,
+ errorLoadingMessages: errorLoadingMessages ?? this.errorLoadingMessages,
+ noMessagesYet: noMessagesYet ?? this.noMessagesYet,
+ tapToRetry: tapToRetry ?? this.tapToRetry,
+ search: search ?? this.search,
+ clear: clear ?? this.clear,
+ noResultsFound: noResultsFound ?? this.noResultsFound,
+ );
+ }
+
+ /// Formats [source] into a string that describes an unsupported image source.
+ ///
+ /// The formatted string includes the value of [source] and the string
+ /// representation of [unsupportedImageSource].
+ ///
+ /// The [source] parameter is the image source that is not supported.
+ ///
+ /// Returns a string that describes the unsupported image source.
+ String formatUnsupportedImageSource(String source) =>
+ '$unsupportedImageSource: $source';
+
+ /// Formats [error] into a string that describes an error occurred while
+ /// picking an image.
+ ///
+ /// The formatted string includes the value of [error] and the string
+ /// representation of [unableToPickImage].
+ ///
+ /// The [error] parameter is the error that occurred.
+ ///
+ /// Returns a string that describes the error.
+ String formatUnableToPickImage(String error) => '$unableToPickImage: $error';
+
+ /// Formats [error] into a string that describes an error occurred while
+ /// picking a file.
+ ///
+ /// The formatted string includes the value of [error] and the string
+ /// representation of [unableToPickFile].
+ ///
+ /// The [error] parameter is the error that occurred.
+ ///
+ /// Returns a string that describes the error.
+ String formatUnableToPickFile(String error) => '$unableToPickFile: $error';
+
+ /// Formats [error] into a string that describes an error occurred while
+ /// picking a url.
+ ///
+ /// The formatted string includes the value of [error] and the string
+ /// representation of [unableToPickUrl].
+ ///
+ /// The [error] parameter is the error that occurred.
+ ///
+ /// Returns a string that describes the error.
+ String formatUnableToPickUrl(String error) => '$unableToPickUrl: $error';
+}
diff --git a/lib/src/strings/strings.dart b/lib/src/strings/strings.dart
new file mode 100644
index 00000000..281f8376
--- /dev/null
+++ b/lib/src/strings/strings.dart
@@ -0,0 +1 @@
+export 'llm_chat_view_strings.dart';
diff --git a/lib/src/styles/action_button_style.dart b/lib/src/styles/action_button_style.dart
index 4e86875f..93999716 100644
--- a/lib/src/styles/action_button_style.dart
+++ b/lib/src/styles/action_button_style.dart
@@ -4,6 +4,7 @@
import 'package:flutter/widgets.dart';
+import '../strings/llm_chat_view_strings.dart';
import 'action_button_type.dart';
import 'tookit_icons.dart';
import 'toolkit_colors.dart';
@@ -42,11 +43,19 @@ class ActionButtonStyle {
);
/// Provides default style for icon buttons.
- factory ActionButtonStyle.defaultStyle(ActionButtonType type) =>
- ActionButtonStyle._lightStyle(type);
+ factory ActionButtonStyle.defaultStyle(
+ ActionButtonType type, {
+ LlmChatViewStrings? strings,
+ }) {
+ final resolvedStrings = strings ?? LlmChatViewStrings.defaults;
+ return ActionButtonStyle._lightStyle(type, resolvedStrings);
+ }
/// Provides default light style for icon buttons.
- factory ActionButtonStyle._lightStyle(ActionButtonType type) {
+ factory ActionButtonStyle._lightStyle(
+ ActionButtonType type,
+ LlmChatViewStrings strings,
+ ) {
IconData? icon;
var color = ToolkitColors.darkIcon;
var bgColor = ToolkitColors.lightButtonBackground;
@@ -56,56 +65,56 @@ class ActionButtonStyle {
switch (type) {
case ActionButtonType.add:
icon = ToolkitIcons.add;
- text = 'Add Attachment';
+ text = strings.addAttachment;
case ActionButtonType.attachFile:
icon = ToolkitIcons.attach_file;
color = ToolkitColors.darkIcon;
bgColor = ToolkitColors.transparent;
- text = 'Attach File';
+ text = strings.attachFile;
textStyle = ToolkitTextStyles.body2;
case ActionButtonType.camera:
icon = ToolkitIcons.camera_alt;
color = ToolkitColors.darkIcon;
bgColor = ToolkitColors.transparent;
- text = 'Take Photo';
+ text = strings.takePhoto;
textStyle = ToolkitTextStyles.body2;
case ActionButtonType.stop:
icon = ToolkitIcons.stop;
- text = 'Stop';
+ text = strings.stop;
case ActionButtonType.close:
icon = ToolkitIcons.close;
color = ToolkitColors.whiteIcon;
bgColor = ToolkitColors.darkButtonBackground;
- text = 'Close';
+ text = strings.close;
case ActionButtonType.cancel:
icon = ToolkitIcons.close;
color = ToolkitColors.whiteIcon;
bgColor = ToolkitColors.darkButtonBackground;
- text = 'Cancel';
+ text = strings.cancel;
case ActionButtonType.copy:
icon = ToolkitIcons.content_copy;
color = ToolkitColors.whiteIcon;
bgColor = ToolkitColors.darkButtonBackground;
- text = 'Copy to Clipboard';
+ text = strings.copyToClipboard;
case ActionButtonType.edit:
icon = ToolkitIcons.edit;
color = ToolkitColors.whiteIcon;
bgColor = ToolkitColors.darkButtonBackground;
- text = 'Edit Message';
+ text = strings.editMessage;
case ActionButtonType.gallery:
icon = ToolkitIcons.image;
color = ToolkitColors.darkIcon;
bgColor = ToolkitColors.transparent;
- text = 'Attach Image';
+ text = strings.attachImage;
textStyle = ToolkitTextStyles.body2;
case ActionButtonType.record:
icon = ToolkitIcons.mic;
- text = 'Record Audio';
+ text = strings.recordAudio;
case ActionButtonType.submit:
icon = ToolkitIcons.submit_icon;
color = ToolkitColors.whiteIcon;
bgColor = ToolkitColors.darkButtonBackground;
- text = 'Submit Message';
+ text = strings.submitMessage;
case ActionButtonType.disabled:
icon = ToolkitIcons.submit_icon;
color = ToolkitColors.darkIcon;
@@ -115,12 +124,12 @@ class ActionButtonStyle {
icon = ToolkitIcons.close;
color = ToolkitColors.whiteIcon;
bgColor = ToolkitColors.greyBackground;
- text = 'Close Menu';
+ text = strings.closeMenu;
case ActionButtonType.url:
icon = null; // Placeholder for URL icon
color = ToolkitColors.darkIcon;
bgColor = ToolkitColors.transparent;
- text = 'Attach Link';
+ text = strings.attachFile;
textStyle = ToolkitTextStyles.body2;
}
diff --git a/lib/src/styles/llm_chat_view_style.dart b/lib/src/styles/llm_chat_view_style.dart
index ef99b239..14ff42fd 100644
--- a/lib/src/styles/llm_chat_view_style.dart
+++ b/lib/src/styles/llm_chat_view_style.dart
@@ -4,6 +4,7 @@
import 'package:flutter/widgets.dart';
+import '../strings/strings.dart';
import 'action_button_style.dart';
import 'action_button_type.dart';
import 'chat_input_style.dart';
@@ -46,6 +47,7 @@ class LlmChatViewStyle {
this.padding,
this.margin,
this.messageSpacing,
+ this.strings,
});
/// Resolves the provided [style] with the [defaultStyle].
@@ -81,58 +83,93 @@ class LlmChatViewStyle {
),
addButtonStyle: ActionButtonStyle.resolve(
style?.addButtonStyle,
- defaultStyle: ActionButtonStyle.defaultStyle(ActionButtonType.add),
+ defaultStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.add,
+ strings: style?.strings,
+ ),
),
attachFileButtonStyle: ActionButtonStyle.resolve(
style?.attachFileButtonStyle,
defaultStyle: ActionButtonStyle.defaultStyle(
ActionButtonType.attachFile,
+ strings: style?.strings,
),
),
cameraButtonStyle: ActionButtonStyle.resolve(
style?.cameraButtonStyle,
- defaultStyle: ActionButtonStyle.defaultStyle(ActionButtonType.camera),
+ defaultStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.camera,
+ strings: style?.strings,
+ ),
),
stopButtonStyle: ActionButtonStyle.resolve(
style?.stopButtonStyle,
- defaultStyle: ActionButtonStyle.defaultStyle(ActionButtonType.stop),
+ defaultStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.stop,
+ strings: style?.strings,
+ ),
),
closeButtonStyle: ActionButtonStyle.resolve(
style?.closeButtonStyle,
- defaultStyle: ActionButtonStyle.defaultStyle(ActionButtonType.close),
+ defaultStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.close,
+ strings: style?.strings,
+ ),
),
cancelButtonStyle: ActionButtonStyle.resolve(
style?.cancelButtonStyle,
- defaultStyle: ActionButtonStyle.defaultStyle(ActionButtonType.cancel),
+ defaultStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.cancel,
+ strings: style?.strings,
+ ),
),
copyButtonStyle: ActionButtonStyle.resolve(
style?.copyButtonStyle,
- defaultStyle: ActionButtonStyle.defaultStyle(ActionButtonType.copy),
+ defaultStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.copy,
+ strings: style?.strings,
+ ),
),
editButtonStyle: ActionButtonStyle.resolve(
style?.editButtonStyle,
- defaultStyle: ActionButtonStyle.defaultStyle(ActionButtonType.edit),
+ defaultStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.edit,
+ strings: style?.strings,
+ ),
),
galleryButtonStyle: ActionButtonStyle.resolve(
style?.galleryButtonStyle,
- defaultStyle: ActionButtonStyle.defaultStyle(ActionButtonType.gallery),
+ defaultStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.gallery,
+ strings: style?.strings,
+ ),
),
recordButtonStyle: ActionButtonStyle.resolve(
style?.recordButtonStyle,
- defaultStyle: ActionButtonStyle.defaultStyle(ActionButtonType.record),
+ defaultStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.record,
+ strings: style?.strings,
+ ),
),
submitButtonStyle: ActionButtonStyle.resolve(
style?.submitButtonStyle,
- defaultStyle: ActionButtonStyle.defaultStyle(ActionButtonType.submit),
+ defaultStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.submit,
+ strings: style?.strings,
+ ),
),
disabledButtonStyle: ActionButtonStyle.resolve(
style?.disabledButtonStyle,
- defaultStyle: ActionButtonStyle.defaultStyle(ActionButtonType.disabled),
+ defaultStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.disabled,
+ strings: style?.strings,
+ ),
),
closeMenuButtonStyle: ActionButtonStyle.resolve(
style?.closeMenuButtonStyle,
defaultStyle: ActionButtonStyle.defaultStyle(
ActionButtonType.closeMenu,
+ strings: style?.strings,
),
),
actionButtonBarDecoration:
@@ -148,7 +185,10 @@ class LlmChatViewStyle {
),
urlButtonStyle: ActionButtonStyle.resolve(
style?.urlButtonStyle,
- defaultStyle: ActionButtonStyle.defaultStyle(ActionButtonType.url),
+ defaultStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.url,
+ strings: style?.strings,
+ ),
),
padding: style?.padding ?? defaultStyle.padding,
margin: style?.margin ?? defaultStyle.margin,
@@ -167,24 +207,53 @@ class LlmChatViewStyle {
userMessageStyle: UserMessageStyle.defaultStyle(),
llmMessageStyle: LlmMessageStyle.defaultStyle(),
chatInputStyle: ChatInputStyle.defaultStyle(),
- addButtonStyle: ActionButtonStyle.defaultStyle(ActionButtonType.add),
- stopButtonStyle: ActionButtonStyle.defaultStyle(ActionButtonType.stop),
- recordButtonStyle: ActionButtonStyle.defaultStyle(ActionButtonType.record),
- submitButtonStyle: ActionButtonStyle.defaultStyle(ActionButtonType.submit),
+ addButtonStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.add,
+ strings: LlmChatViewStrings.defaults,
+ ),
+ stopButtonStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.stop,
+ strings: LlmChatViewStrings.defaults,
+ ),
+ recordButtonStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.record,
+ strings: LlmChatViewStrings.defaults,
+ ),
+ submitButtonStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.submit,
+ strings: LlmChatViewStrings.defaults,
+ ),
closeMenuButtonStyle: ActionButtonStyle.defaultStyle(
ActionButtonType.closeMenu,
+ strings: LlmChatViewStrings.defaults,
),
attachFileButtonStyle: ActionButtonStyle.defaultStyle(
ActionButtonType.attachFile,
+ strings: LlmChatViewStrings.defaults,
),
galleryButtonStyle: ActionButtonStyle.defaultStyle(
ActionButtonType.gallery,
),
- cameraButtonStyle: ActionButtonStyle.defaultStyle(ActionButtonType.camera),
- closeButtonStyle: ActionButtonStyle.defaultStyle(ActionButtonType.close),
- cancelButtonStyle: ActionButtonStyle.defaultStyle(ActionButtonType.cancel),
- copyButtonStyle: ActionButtonStyle.defaultStyle(ActionButtonType.copy),
- editButtonStyle: ActionButtonStyle.defaultStyle(ActionButtonType.edit),
+ cameraButtonStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.camera,
+ strings: LlmChatViewStrings.defaults,
+ ),
+ closeButtonStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.close,
+ strings: LlmChatViewStrings.defaults,
+ ),
+ cancelButtonStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.cancel,
+ strings: LlmChatViewStrings.defaults,
+ ),
+ copyButtonStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.copy,
+ strings: LlmChatViewStrings.defaults,
+ ),
+ editButtonStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.edit,
+ strings: LlmChatViewStrings.defaults,
+ ),
actionButtonBarDecoration: BoxDecoration(
color: ToolkitColors.darkButtonBackground,
borderRadius: BorderRadius.circular(20),
@@ -192,7 +261,10 @@ class LlmChatViewStyle {
fileAttachmentStyle: FileAttachmentStyle.defaultStyle(),
suggestionStyle: SuggestionStyle.defaultStyle(),
voiceNoteRecorderStyle: VoiceNoteRecorderStyle.defaultStyle(),
- urlButtonStyle: ActionButtonStyle.defaultStyle(ActionButtonType.url),
+ urlButtonStyle: ActionButtonStyle.defaultStyle(
+ ActionButtonType.url,
+ strings: LlmChatViewStrings.defaults,
+ ),
);
/// Creates a copy of this style with the given fields replaced by the new
@@ -340,4 +412,7 @@ class LlmChatViewStyle {
/// Spacing between messages.
final double? messageSpacing;
+
+ /// Custom strings for the chat view.
+ final LlmChatViewStrings? strings;
}
diff --git a/lib/src/utility.dart b/lib/src/utility.dart
index c2caf118..feecfab4 100644
--- a/lib/src/utility.dart
+++ b/lib/src/utility.dart
@@ -50,10 +50,14 @@ final isMobile = UniversalPlatform.isAndroid || UniversalPlatform.isIOS;
///
/// Returns: A [Future] that completes when the text has been copied to the
/// clipboard and the confirmation message has been shown.
-Future copyToClipboard(BuildContext context, String text) async {
+Future copyToClipboard(
+ BuildContext context,
+ String text,
+ String message,
+) async {
await Clipboard.setData(ClipboardData(text: text));
if (context.mounted) {
- AdaptiveSnackBar.show(context, 'Message copied to clipboard');
+ AdaptiveSnackBar.show(context, message);
}
}
diff --git a/lib/src/views/chat_input/attachments_action_bar.dart b/lib/src/views/chat_input/attachments_action_bar.dart
index 58e76971..edd327d6 100644
--- a/lib/src/views/chat_input/attachments_action_bar.dart
+++ b/lib/src/views/chat_input/attachments_action_bar.dart
@@ -8,7 +8,8 @@ import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart'
show Icons, MenuAnchor, MenuItemButton, MenuStyle;
import 'package:flutter/widgets.dart';
-import 'package:flutter_ai_toolkit/src/dialogs/url_input_dialog.dart';
+import '../../dialogs/url_input_dialog.dart';
+import '../../strings/llm_chat_view_strings.dart';
import 'package:flutter_ai_toolkit/src/utility.dart';
import 'package:image_picker/image_picker.dart';
@@ -50,6 +51,7 @@ class _AttachmentActionBarState extends State {
Widget build(BuildContext context) => ChatViewModelClient(
builder: (context, viewModel, child) {
final chatStyle = LlmChatViewStyle.resolve(viewModel.style);
+ final chatStrings = viewModel.strings;
final menuItems = [
if (_canCamera)
MenuItemButton(
@@ -57,7 +59,7 @@ class _AttachmentActionBarState extends State {
chatStyle.cameraButtonStyle!.icon!,
color: chatStyle.cameraButtonStyle!.iconColor,
),
- onPressed: () => _onCamera(),
+ onPressed: () => _onCamera(chatStrings),
child: Text(
chatStyle.cameraButtonStyle!.text!,
style: chatStyle.cameraButtonStyle!.textStyle,
@@ -68,7 +70,7 @@ class _AttachmentActionBarState extends State {
chatStyle.galleryButtonStyle!.icon!,
color: chatStyle.galleryButtonStyle!.iconColor,
),
- onPressed: () => _onGallery(),
+ onPressed: () => _onGallery(chatStrings),
child: Text(
chatStyle.galleryButtonStyle!.text!,
style: chatStyle.galleryButtonStyle!.textStyle,
@@ -79,7 +81,7 @@ class _AttachmentActionBarState extends State {
chatStyle.attachFileButtonStyle!.icon!,
color: chatStyle.attachFileButtonStyle!.iconColor,
),
- onPressed: () => _onFile(),
+ onPressed: () => _onFile(chatStrings),
child: Text(
chatStyle.attachFileButtonStyle!.text!,
style: chatStyle.attachFileButtonStyle!.textStyle,
@@ -90,7 +92,7 @@ class _AttachmentActionBarState extends State {
Icons.link,
color: chatStyle.urlButtonStyle!.iconColor,
),
- onPressed: () => _onUrl(),
+ onPressed: () => _onUrl(chatStrings),
child: Text(
chatStyle.urlButtonStyle!.text!,
style: chatStyle.urlButtonStyle!.textStyle,
@@ -139,10 +141,15 @@ class _AttachmentActionBarState extends State {
return Offset(0, -estimatedMenuHeight);
}
- void _onCamera() => unawaited(_pickImage(ImageSource.camera));
- void _onGallery() => unawaited(_pickImage(ImageSource.gallery));
+ void _onCamera(LlmChatViewStrings chatStrings) =>
+ unawaited(_pickImage(ImageSource.camera, chatStrings));
+ void _onGallery(LlmChatViewStrings chatStrings) =>
+ unawaited(_pickImage(ImageSource.gallery, chatStrings));
- Future _pickImage(ImageSource source) async {
+ Future _pickImage(
+ ImageSource source,
+ LlmChatViewStrings chatStrings,
+ ) async {
assert(
source == ImageSource.camera || source == ImageSource.gallery,
'Unsupported image source: $source',
@@ -162,38 +169,41 @@ class _AttachmentActionBarState extends State {
widget.onAttachments([await ImageFileAttachment.fromFile(pic)]);
}
} on Exception catch (ex) {
+ final message = chatStrings.formatUnableToPickImage(ex.toString());
if (context.mounted) {
// I just checked this! ^^^
// ignore: use_build_context_synchronously
- AdaptiveSnackBar.show(context, 'Unable to pick an image: $ex');
+ AdaptiveSnackBar.show(context, message);
}
}
}
- Future _onFile() async {
+ Future _onFile(LlmChatViewStrings chatStrings) async {
try {
final files = await openFiles();
final attachments = await Future.wait(files.map(FileAttachment.fromFile));
widget.onAttachments(attachments);
} on Exception catch (ex) {
+ final message = chatStrings.formatUnableToPickFile(ex.toString());
if (context.mounted) {
// I just checked this! ^^^
// ignore: use_build_context_synchronously
- AdaptiveSnackBar.show(context, 'Unable to pick a file: $ex');
+ AdaptiveSnackBar.show(context, message);
}
}
}
- Future _onUrl() async {
+ Future _onUrl(LlmChatViewStrings chatStrings) async {
try {
final url = await showUrlInputDialog(context);
if (url == null) return;
widget.onAttachments([LinkAttachment(name: url.name, url: url.url)]);
} on Exception catch (ex) {
+ final message = chatStrings.formatUnableToPickUrl(ex.toString());
if (context.mounted) {
// I just checked this! ^^^
// ignore: use_build_context_synchronously
- AdaptiveSnackBar.show(context, 'Unable to pick a URL: $ex');
+ AdaptiveSnackBar.show(context, message);
}
}
}
diff --git a/lib/src/views/chat_input/chat_input.dart b/lib/src/views/chat_input/chat_input.dart
index 45a886d5..d46b8b50 100644
--- a/lib/src/views/chat_input/chat_input.dart
+++ b/lib/src/views/chat_input/chat_input.dart
@@ -196,6 +196,7 @@ class _ChatInputState extends State {
cancelButtonStyle: _chatStyle!.cancelButtonStyle!,
voiceNoteRecorderStyle:
_chatStyle!.voiceNoteRecorderStyle!,
+ chatStrings: _viewModel!.strings,
),
),
Padding(
@@ -260,7 +261,7 @@ class _ChatInputState extends State {
final file = _waveController.file;
if (file == null) {
- AdaptiveSnackBar.show(context, 'Unable to record audio');
+ AdaptiveSnackBar.show(context, _viewModel!.strings.unableToRecordAudio);
return;
}
diff --git a/lib/src/views/chat_input/editing_indicator.dart b/lib/src/views/chat_input/editing_indicator.dart
index a9560bdf..85f6518b 100644
--- a/lib/src/views/chat_input/editing_indicator.dart
+++ b/lib/src/views/chat_input/editing_indicator.dart
@@ -20,6 +20,7 @@ class EditingIndicator extends StatelessWidget {
const EditingIndicator({
required this.onCancelEdit,
required this.cancelButtonStyle,
+ required this.editingTitle,
super.key,
});
@@ -29,6 +30,9 @@ class EditingIndicator extends StatelessWidget {
/// The style to be applied to the cancel button.
final ActionButtonStyle cancelButtonStyle;
+ /// The title to be displayed in the editing indicator.
+ final String editingTitle;
+
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.only(right: 16),
@@ -38,7 +42,7 @@ class EditingIndicator extends StatelessWidget {
spacing: 6,
children: [
Text(
- 'Editing',
+ editingTitle,
style: ToolkitTextStyles.label.copyWith(
color: invertColor(cancelButtonStyle.iconColor),
),
diff --git a/lib/src/views/chat_input/text_or_audio_input.dart b/lib/src/views/chat_input/text_or_audio_input.dart
index 05732397..72d74002 100644
--- a/lib/src/views/chat_input/text_or_audio_input.dart
+++ b/lib/src/views/chat_input/text_or_audio_input.dart
@@ -1,5 +1,6 @@
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
+import '../../strings/llm_chat_view_strings.dart';
import 'package:waveform_recorder/waveform_recorder.dart';
import '../../styles/styles.dart';
@@ -39,6 +40,7 @@ class TextOrAudioInput extends StatelessWidget {
required InputState inputState,
required ActionButtonStyle cancelButtonStyle,
required VoiceNoteRecorderStyle voiceNoteRecorderStyle,
+ required LlmChatViewStrings chatStrings,
}) : _cancelButtonStyle = cancelButtonStyle,
_inputState = inputState,
_autofocus = autofocus,
@@ -49,7 +51,8 @@ class TextOrAudioInput extends StatelessWidget {
_onCancelEdit = onCancelEdit,
_waveController = waveController,
_inputStyle = inputStyle,
- _voiceNoteRecorderStyle = voiceNoteRecorderStyle;
+ _voiceNoteRecorderStyle = voiceNoteRecorderStyle,
+ _chatStrings = chatStrings;
final ChatInputStyle _inputStyle;
final WaveformRecorderController _waveController;
@@ -62,6 +65,7 @@ class TextOrAudioInput extends StatelessWidget {
final InputState _inputState;
final ActionButtonStyle _cancelButtonStyle;
final VoiceNoteRecorderStyle _voiceNoteRecorderStyle;
+ final LlmChatViewStrings _chatStrings;
static const _minInputHeight = 48.0;
static const _maxInputHeight = 144.0;
@@ -124,6 +128,7 @@ class TextOrAudioInput extends StatelessWidget {
? EditingIndicator(
onCancelEdit: _onCancelEdit,
cancelButtonStyle: _cancelButtonStyle,
+ editingTitle: _chatStrings.editing,
)
: const SizedBox(),
),
diff --git a/lib/src/views/chat_message_view/adaptive_copy_text.dart b/lib/src/views/chat_message_view/adaptive_copy_text.dart
index 40b47403..b859bc22 100644
--- a/lib/src/views/chat_message_view/adaptive_copy_text.dart
+++ b/lib/src/views/chat_message_view/adaptive_copy_text.dart
@@ -7,9 +7,9 @@ import 'package:flutter/material.dart'
SelectionArea,
DefaultWidgetsLocalizations;
import 'package:flutter/widgets.dart';
+import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
import 'package:flutter_context_menu/flutter_context_menu.dart';
-import '../../styles/llm_chat_view_style.dart';
import '../../utility.dart';
/// A widget that displays text with adaptive copy functionality.
@@ -30,6 +30,7 @@ class AdaptiveCopyText extends StatelessWidget {
required this.clipboardText,
required this.child,
required this.chatStyle,
+ required this.chatStrings,
this.onEdit,
super.key,
});
@@ -46,20 +47,30 @@ class AdaptiveCopyText extends StatelessWidget {
/// The style information for the chat.
final LlmChatViewStyle chatStyle;
+ /// The strings used for text in the chat interface.
+ final LlmChatViewStrings chatStrings;
+
@override
Widget build(BuildContext context) {
final contextMenu = ContextMenu(
entries: [
if (onEdit != null)
MenuItem(
- label: const Text('Edit'),
+ label: Text(chatStrings.edit),
icon: Icon(chatStyle.editButtonStyle!.icon),
onSelected: (_) => onEdit?.call(),
),
MenuItem(
- label: const Text('Copy'),
+ label: Text(chatStrings.copy),
icon: Icon(chatStyle.copyButtonStyle!.icon),
- onSelected: (_) => unawaited(copyToClipboard(context, clipboardText)),
+ onSelected:
+ (_) => unawaited(
+ copyToClipboard(
+ context,
+ clipboardText,
+ chatStrings.copyToClipboard,
+ ),
+ ),
),
],
);
diff --git a/lib/src/views/chat_message_view/hovering_buttons.dart b/lib/src/views/chat_message_view/hovering_buttons.dart
index 73f60a95..cb37b1e0 100644
--- a/lib/src/views/chat_message_view/hovering_buttons.dart
+++ b/lib/src/views/chat_message_view/hovering_buttons.dart
@@ -20,6 +20,7 @@ class HoveringButtons extends StatelessWidget {
required this.isUserMessage,
required this.child,
this.clipboardText,
+ required this.clipboardMessage,
this.onEdit,
super.key,
});
@@ -33,6 +34,9 @@ class HoveringButtons extends StatelessWidget {
/// The text to be copied to the clipboard.
final String? clipboardText;
+ ///The text to be shown when copying to the clipboard.
+ final String clipboardMessage;
+
/// The child widget over which the buttons will hover.
final Widget child;
@@ -86,6 +90,7 @@ class HoveringButtons extends StatelessWidget {
copyToClipboard(
context,
clipboardText!,
+ clipboardMessage,
),
),
child: Icon(
diff --git a/lib/src/views/chat_message_view/llm_message_view.dart b/lib/src/views/chat_message_view/llm_message_view.dart
index 15801552..abbc57e1 100644
--- a/lib/src/views/chat_message_view/llm_message_view.dart
+++ b/lib/src/views/chat_message_view/llm_message_view.dart
@@ -42,6 +42,7 @@ class LlmMessageView extends StatelessWidget {
final text = message.text;
final chatStyle = LlmChatViewStyle.resolve(viewModel.style);
final llmStyle = LlmMessageStyle.resolve(chatStyle.llmMessageStyle);
+ final chatString = viewModel.strings;
return Flexible(
flex: llmStyle.flex,
@@ -70,6 +71,7 @@ class LlmMessageView extends StatelessWidget {
isUserMessage: false,
chatStyle: chatStyle,
clipboardText: text,
+ clipboardMessage: chatString.copyToClipboard,
child: Container(
width: double.infinity,
decoration: llmStyle.decoration,
@@ -87,6 +89,7 @@ class LlmMessageView extends StatelessWidget {
: AdaptiveCopyText(
clipboardText: text,
chatStyle: chatStyle,
+ chatStrings: chatString,
child:
isWelcomeMessage ||
viewModel.responseBuilder == null
diff --git a/lib/src/views/chat_message_view/user_message_view.dart b/lib/src/views/chat_message_view/user_message_view.dart
index 56c55ea6..91e3e4fb 100644
--- a/lib/src/views/chat_message_view/user_message_view.dart
+++ b/lib/src/views/chat_message_view/user_message_view.dart
@@ -55,6 +55,7 @@ class UserMessageView extends StatelessWidget {
final userStyle = UserMessageStyle.resolve(
chatStyle.userMessageStyle,
);
+ final chatStrings = viewModel.strings;
return Align(
alignment: Alignment.topRight,
@@ -64,6 +65,7 @@ class UserMessageView extends StatelessWidget {
isUserMessage: true,
chatStyle: chatStyle,
clipboardText: text,
+ clipboardMessage: chatStrings.copyToClipboard,
onEdit: onEdit,
child: DecoratedBox(
decoration: userStyle.decoration!,
@@ -77,6 +79,7 @@ class UserMessageView extends StatelessWidget {
child: AdaptiveCopyText(
chatStyle: chatStyle,
clipboardText: text,
+ chatStrings: chatStrings,
onEdit: onEdit,
child: Text(text, style: userStyle.textStyle),
),
diff --git a/lib/src/views/llm_chat_view/llm_chat_view.dart b/lib/src/views/llm_chat_view/llm_chat_view.dart
index 72c9084d..1b074436 100644
--- a/lib/src/views/llm_chat_view/llm_chat_view.dart
+++ b/lib/src/views/llm_chat_view/llm_chat_view.dart
@@ -16,6 +16,7 @@ import '../../platform_helper/platform_helper.dart' as ph;
import '../../providers/interface/attachments.dart';
import '../../providers/interface/chat_message.dart';
import '../../providers/interface/llm_provider.dart';
+import '../../strings/llm_chat_view_strings.dart';
import '../../styles/llm_chat_view_style.dart';
import '../chat_history_view.dart';
import '../chat_input/chat_input.dart';
@@ -76,6 +77,8 @@ class LlmChatView extends StatefulWidget {
/// during a chat operation. Defaults to 'ERROR'.
/// - [enableAttachments]: Optional. Whether to enable file and image attachments in the chat input.
/// - [enableVoiceNotes]: Optional. Whether to enable voice notes in the chat input.
+ /// - [strings]: Optional. Custom strings for the chat interface. If not provided,
+ /// the default strings will be used.
LlmChatView({
required LlmProvider provider,
LlmChatViewStyle? style,
@@ -91,6 +94,7 @@ class LlmChatView extends StatefulWidget {
this.enableAttachments = true,
this.enableVoiceNotes = true,
this.autofocus,
+ LlmChatViewStrings? strings,
super.key,
}) : viewModel = ChatViewModel(
provider: provider,
@@ -102,8 +106,15 @@ class LlmChatView extends StatefulWidget {
welcomeMessage: welcomeMessage,
enableAttachments: enableAttachments,
enableVoiceNotes: enableVoiceNotes,
+ strings: strings ?? LlmChatViewStrings.defaults,
);
+ /// The strings used throughout the chat interface.
+ ///
+ /// This provides access to all the text strings used in the chat interface,
+ /// allowing for easy customization and internationalization.
+ LlmChatViewStrings get strings => viewModel.strings;
+
/// Whether to enable file and image attachments in the chat input.
///
/// When set to false, the attachment button and related functionality will be