diff --git a/frontend/lib/models/step_request_body.dart b/frontend/lib/models/step_request_body.dart index 6f8fb7cb16ab..23e7521fcd5a 100644 --- a/frontend/lib/models/step_request_body.dart +++ b/frontend/lib/models/step_request_body.dart @@ -1,10 +1,13 @@ class StepRequestBody { - final String input; + final String? input; final Map? additionalInput; StepRequestBody({required this.input, this.additionalInput}); Map toJson() { + if (input == null && additionalInput == null) { + return {}; + } return {'input': input, 'additional_input': additionalInput}; } } diff --git a/frontend/lib/viewmodels/chat_viewmodel.dart b/frontend/lib/viewmodels/chat_viewmodel.dart index 5c8fb3cae535..f85c9d5eacc3 100644 --- a/frontend/lib/viewmodels/chat_viewmodel.dart +++ b/frontend/lib/viewmodels/chat_viewmodel.dart @@ -10,6 +10,14 @@ class ChatViewModel with ChangeNotifier { List _chats = []; String? _currentTaskId; + bool _isContinuousMode = false; + + bool get isContinuousMode => _isContinuousMode; + set isContinuousMode(bool value) { + _isContinuousMode = value; + notifyListeners(); + } + ChatViewModel(this._chatService); /// Returns the current list of chats. @@ -95,7 +103,7 @@ class ChatViewModel with ChangeNotifier { } /// Sends a chat message for a specific task. - void sendChatMessage(String message) async { + void sendChatMessage(String? message) async { if (_currentTaskId == null) { print("Error: Task ID is not set."); return; @@ -112,13 +120,17 @@ class ChatViewModel with ChangeNotifier { Step executedStep = Step.fromMap(executedStepResponse); // Create a Chat object for the user message - final userChat = Chat( - id: executedStep.stepId, - taskId: executedStep.taskId, - message: executedStep.input, - timestamp: DateTime.now(), - messageType: MessageType.user, - ); + if (executedStep.input.isNotEmpty) { + final userChat = Chat( + id: executedStep.stepId, + taskId: executedStep.taskId, + message: executedStep.input, + timestamp: DateTime.now(), + messageType: MessageType.user, + ); + + _chats.add(userChat); + } // Create a Chat object for the agent message final agentChat = Chat( @@ -129,13 +141,15 @@ class ChatViewModel with ChangeNotifier { messageType: MessageType.agent, jsonResponse: executedStepResponse); - // Add the user and agent chats to the list - _chats.add(userChat); _chats.add(agentChat); // Notify UI of the new chats notifyListeners(); + if (_isContinuousMode) { + sendChatMessage(null); + } + print("Chats added for task ID: $_currentTaskId"); } catch (error) { // TODO: Bubble up errors to UI diff --git a/frontend/lib/viewmodels/task_viewmodel.dart b/frontend/lib/viewmodels/task_viewmodel.dart index 4b9517b9b842..5423c1812489 100644 --- a/frontend/lib/viewmodels/task_viewmodel.dart +++ b/frontend/lib/viewmodels/task_viewmodel.dart @@ -73,4 +73,11 @@ class TaskViewModel with ChangeNotifier { throw ArgumentError(errorMessage); } } + + /// Deselects the currently selected task. + void deselectTask() { + _selectedTask = null; + print("Deselected the current task."); + notifyListeners(); // Notify listeners to rebuild UI + } } diff --git a/frontend/lib/views/chat/chat_input_field.dart b/frontend/lib/views/chat/chat_input_field.dart index 560e9069fe3f..4901408aafce 100644 --- a/frontend/lib/views/chat/chat_input_field.dart +++ b/frontend/lib/views/chat/chat_input_field.dart @@ -3,10 +3,14 @@ import 'package:flutter/material.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; const ChatInputField({ Key? key, required this.onSendPressed, + required this.onContinuousModePressed, + this.isContinuousMode = false, }) : super(key: key); @override @@ -16,6 +20,23 @@ class ChatInputField extends StatefulWidget { class _ChatInputFieldState extends State { // Controller for the TextField to manage its content final TextEditingController _controller = TextEditingController(); + final FocusNode _focusNode = 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(); + } @override Widget build(BuildContext context) { @@ -51,21 +72,48 @@ class _ChatInputFieldState extends State { 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: IconButton( - splashRadius: 0.1, - icon: const Icon(Icons.send), - onPressed: () { - // TODO: We allow empty messages? - if (_controller.text.isNotEmpty) { - widget.onSendPressed(_controller.text); - _controller.clear(); - } - }, + 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(); + }, + ), + ), + // TODO: Include pop up to explain continuous mode reprecussions + Tooltip( + message: widget.isContinuousMode + ? '' + : 'Enable continuous mode', + child: IconButton( + splashRadius: 0.1, + icon: Icon(widget.isContinuousMode + ? Icons.pause + : Icons.fast_forward), + onPressed: () { + if (!widget.isContinuousMode) { + widget.onSendPressed(_controller.text); + _controller.clear(); + _focusNode.unfocus(); + } + widget.onContinuousModePressed(); + }, + ), + ) + ], ), ), ), diff --git a/frontend/lib/views/chat/chat_view.dart b/frontend/lib/views/chat/chat_view.dart index d3a5bfdb5291..565ff2854e0c 100644 --- a/frontend/lib/views/chat/chat_view.dart +++ b/frontend/lib/views/chat/chat_view.dart @@ -56,13 +56,20 @@ class _ChatViewState extends State { child: ChatInputField( onSendPressed: (message) async { if (widget.viewModel.currentTaskId != null) { - widget.viewModel.sendChatMessage(message); + widget.viewModel + .sendChatMessage((message == "") ? null : message); } else { String newTaskId = await taskViewModel.createTask(message); widget.viewModel.setCurrentTaskId(newTaskId); - widget.viewModel.sendChatMessage(message); + widget.viewModel + .sendChatMessage((message == "") ? null : message); } }, + onContinuousModePressed: () { + widget.viewModel.isContinuousMode = + !widget.viewModel.isContinuousMode; + }, + isContinuousMode: widget.viewModel.isContinuousMode, ), ), ], diff --git a/frontend/lib/views/task/task_view.dart b/frontend/lib/views/task/task_view.dart index f681f536ea0b..73d64b48f98b 100644 --- a/frontend/lib/views/task/task_view.dart +++ b/frontend/lib/views/task/task_view.dart @@ -46,6 +46,7 @@ class _TaskViewState extends State { final chatViewModel = Provider.of(context, listen: false); chatViewModel.clearCurrentTaskAndChats(); + widget.viewModel.deselectTask(); print( 'New Task button pressed, cleared current task ID and chats'); },