diff options
Diffstat (limited to 'frontend/lib/views/chat/chat_input_field.dart')
-rw-r--r-- | frontend/lib/views/chat/chat_input_field.dart | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/frontend/lib/views/chat/chat_input_field.dart b/frontend/lib/views/chat/chat_input_field.dart new file mode 100644 index 000000000..b46badd61 --- /dev/null +++ b/frontend/lib/views/chat/chat_input_field.dart @@ -0,0 +1,167 @@ +import 'package:auto_gpt_flutter_client/viewmodels/chat_viewmodel.dart'; +import 'package:auto_gpt_flutter_client/views/chat/continuous_mode_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class ChatInputField extends StatefulWidget { + // Callback to be triggered when the send button is pressed + final Function(String) onSendPressed; + final Function() onContinuousModePressed; + final bool isContinuousMode; + // TODO: Create a view model for this class and remove the ChatViewModel + final ChatViewModel viewModel; + + const ChatInputField({ + Key? key, + required this.onSendPressed, + required this.onContinuousModePressed, + this.isContinuousMode = false, + required this.viewModel, + }) : super(key: key); + + @override + _ChatInputFieldState createState() => _ChatInputFieldState(); +} + +class _ChatInputFieldState extends State<ChatInputField> { + // Controller for the TextField to manage its content + final TextEditingController _controller = TextEditingController(); + final FocusNode _focusNode = FocusNode(); + final FocusNode _throwawayFocusNode = FocusNode(); + + @override + void initState() { + super.initState(); + _focusNode.addListener(() { + if (_focusNode.hasFocus && widget.isContinuousMode) { + widget.onContinuousModePressed(); + } + }); + } + + @override + void dispose() { + _focusNode.dispose(); // Dispose of the FocusNode when you're done. + super.dispose(); + } + + Future<void> _presentContinuousModeDialogIfNeeded() async { + final showContinuousModeDialog = await widget.viewModel.prefsService + .getBool('showContinuousModeDialog') ?? + true; + + FocusScope.of(context).requestFocus(_throwawayFocusNode); + if (showContinuousModeDialog) { + showDialog( + context: context, + builder: (BuildContext context) { + return ContinuousModeDialog( + onProceed: () { + Navigator.of(context).pop(); + _executeContinuousMode(); + }, + onCheckboxChanged: (bool value) async { + await widget.viewModel.prefsService + .setBool('showContinuousModeDialog', !value); + }, + ); + }, + ); + } else { + _executeContinuousMode(); + } + } + + void _executeContinuousMode() { + if (!widget.isContinuousMode) { + widget.onSendPressed(_controller.text); + _controller.clear(); + _focusNode.unfocus(); + } + widget.onContinuousModePressed(); + } + + @override + Widget build(BuildContext context) { + // Using LayoutBuilder to provide the current constraints of the widget, + // ensuring it rebuilds when the window size changes + return LayoutBuilder( + builder: (context, constraints) { + // Calculate the width of the chat view based on the constraints provided + double chatViewWidth = constraints.maxWidth; + + // Determine the width of the input field based on the chat view width. + // If the chat view width is 1000 or more, the input width will be 900. + // Otherwise, the input width will be the chat view width minus 40. + double inputWidth = (chatViewWidth >= 1000) ? 900 : chatViewWidth - 40; + + return Container( + width: inputWidth, + // Defining the minimum and maximum height for the TextField container + constraints: const BoxConstraints( + minHeight: 50, + maxHeight: 400, + ), + // Styling the container with a border and rounded corners + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.black, width: 0.5), + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric(horizontal: 8), + // Using SingleChildScrollView to ensure the TextField can scroll + // when the content exceeds its maximum height + child: SingleChildScrollView( + reverse: true, + child: TextField( + controller: _controller, + focusNode: _focusNode, + // Allowing the TextField to expand vertically and accommodate multiple lines + maxLines: null, + decoration: InputDecoration( + hintText: 'Type a message...', + border: InputBorder.none, + suffixIcon: Row( + mainAxisSize: MainAxisSize.min, // Set to minimum space + children: [ + if (!widget.isContinuousMode) + Tooltip( + message: 'Send a single message', + child: IconButton( + splashRadius: 0.1, + icon: const Icon(Icons.send), + onPressed: () { + widget.onSendPressed(_controller.text); + _controller.clear(); + }, + ), + ), + Tooltip( + message: widget.isContinuousMode + ? '' + : 'Enable continuous mode', + child: IconButton( + splashRadius: 0.1, + icon: Icon(widget.isContinuousMode + ? Icons.pause + : Icons.fast_forward), + onPressed: () { + // TODO: All of this logic should be handled at a higher level in the widget tree. Temporary + if (!widget.isContinuousMode) { + _presentContinuousModeDialogIfNeeded(); + } else { + widget.onContinuousModePressed(); + } + }, + ), + ) + ], + ), + ), + ), + ), + ); + }, + ); + } +} |