mirror of
https://github.com/aljazceru/Auto-GPT.git
synced 2025-12-17 14:04:27 +01:00
Integrate TaskQueueView and Enhance SkillTree Functionality (#5206)
* Add TestQueueView to Main Layout This commit integrates the TestQueueView into the main layout. The layout now conditionally displays the TestQueueView based on whether a node in the SkillTree is selected. - TestQueueView appears when a SkillTree node is selected. - Main layout adjusts to accommodate TestQueueView alongside SkillTreeView and ChatView. - Implemented responsive layout logic to manage the widths of the different views based on the screen width and the state of the SkillTree. * Extend SkillTreeViewModel to Track Selected Node Hierarchy This commit enhances the SkillTreeViewModel to maintain a list of nodes that form a hierarchy from the currently selected node to the root. This allows for more interactive and informative views that can leverage this hierarchical data. - Added a new property `selectedNodeHierarchy` to keep track of the node hierarchy. - Modified the `toggleNodeSelection` method to populate or clear `selectedNodeHierarchy` based on node selection. - Introduced a new method `populateSelectedNodeHierarchy` to build the hierarchy from the selected node to the root. * Extract skill tree view model reset state to method * Implement UI enhancements for TaskQueueView This commit introduces several UI improvements to the TaskQueueView: - Tiles are padded 20 units from both the leading and trailing edges. - Tiles now have a white background. - Added a thin black border to the tiles. - Incorporated a slight corner radius for the tiles. - Centered the title and subtitle horizontally within the tiles. - Added a checkmark button with a tooltip at the bottom-right corner for running a suite of tests. These changes aim to improve the user experience and visual appeal of the TaskQueueView. * Make MainLayout a consumer of SkillTreeViewModel
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import 'package:auto_gpt_flutter_client/models/skill_tree/skill_tree_edge.dart';
|
import 'package:auto_gpt_flutter_client/models/skill_tree/skill_tree_edge.dart';
|
||||||
import 'package:auto_gpt_flutter_client/models/skill_tree/skill_tree_node.dart';
|
import 'package:auto_gpt_flutter_client/models/skill_tree/skill_tree_node.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:graphview/GraphView.dart';
|
import 'package:graphview/GraphView.dart';
|
||||||
|
|
||||||
@@ -7,20 +8,17 @@ class SkillTreeViewModel extends ChangeNotifier {
|
|||||||
List<SkillTreeNode> _skillTreeNodes = [];
|
List<SkillTreeNode> _skillTreeNodes = [];
|
||||||
List<SkillTreeEdge> _skillTreeEdges = [];
|
List<SkillTreeEdge> _skillTreeEdges = [];
|
||||||
SkillTreeNode? _selectedNode;
|
SkillTreeNode? _selectedNode;
|
||||||
|
List<SkillTreeNode>? _selectedNodeHierarchy;
|
||||||
|
|
||||||
SkillTreeNode? get selectedNode => _selectedNode;
|
SkillTreeNode? get selectedNode => _selectedNode;
|
||||||
|
List<SkillTreeNode>? get selectedNodeHierarchy => _selectedNodeHierarchy;
|
||||||
|
|
||||||
final Graph graph = Graph()..isTree = true;
|
final Graph graph = Graph()..isTree = true;
|
||||||
BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration();
|
BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration();
|
||||||
|
|
||||||
void initializeSkillTree() {
|
void initializeSkillTree() {
|
||||||
_skillTreeNodes = [];
|
// TODO: Load from JSON
|
||||||
_skillTreeEdges = [];
|
resetState();
|
||||||
_selectedNode = null;
|
|
||||||
|
|
||||||
graph.nodes.clear();
|
|
||||||
graph.edges.clear();
|
|
||||||
|
|
||||||
// Add nodes to _skillTreeNodes
|
// Add nodes to _skillTreeNodes
|
||||||
_skillTreeNodes.addAll([
|
_skillTreeNodes.addAll([
|
||||||
SkillTreeNode(color: 'red', id: 1),
|
SkillTreeNode(color: 'red', id: 1),
|
||||||
@@ -61,17 +59,62 @@ class SkillTreeViewModel extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void resetState() {
|
||||||
|
_skillTreeNodes = [];
|
||||||
|
_skillTreeEdges = [];
|
||||||
|
_selectedNode = null;
|
||||||
|
_selectedNodeHierarchy = null;
|
||||||
|
|
||||||
|
graph.nodes.clear();
|
||||||
|
graph.edges.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void toggleNodeSelection(int nodeId) {
|
void toggleNodeSelection(int nodeId) {
|
||||||
if (_selectedNode?.id == nodeId) {
|
if (_selectedNode?.id == nodeId) {
|
||||||
// Unselect the node if it's already selected
|
// Unselect the node if it's already selected
|
||||||
_selectedNode = null;
|
_selectedNode = null;
|
||||||
|
_selectedNodeHierarchy = null;
|
||||||
} else {
|
} else {
|
||||||
// Select the new node
|
// Select the new node
|
||||||
_selectedNode = _skillTreeNodes.firstWhere((node) => node.id == nodeId);
|
_selectedNode = _skillTreeNodes.firstWhere((node) => node.id == nodeId);
|
||||||
|
populateSelectedNodeHierarchy(nodeId);
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void populateSelectedNodeHierarchy(int startNodeId) {
|
||||||
|
// Initialize an empty list to hold the nodes in the hierarchy.
|
||||||
|
_selectedNodeHierarchy = [];
|
||||||
|
|
||||||
|
// Find the starting node (the selected node) in the skill tree nodes list.
|
||||||
|
SkillTreeNode? currentNode =
|
||||||
|
_skillTreeNodes.firstWhere((node) => node.id == startNodeId);
|
||||||
|
|
||||||
|
// Loop through the tree to populate the hierarchy list.
|
||||||
|
// The loop will continue as long as there's a valid current node.
|
||||||
|
while (currentNode != null) {
|
||||||
|
// Add the current node to the hierarchy list.
|
||||||
|
_selectedNodeHierarchy!.add(currentNode);
|
||||||
|
|
||||||
|
// Find the parent node by looking through the skill tree edges.
|
||||||
|
// We find the edge where the 'to' field matches the ID of the current node.
|
||||||
|
SkillTreeEdge? parentEdge = _skillTreeEdges
|
||||||
|
.firstWhereOrNull((edge) => edge.to == currentNode?.id.toString());
|
||||||
|
|
||||||
|
// If a parent edge is found, find the corresponding parent node.
|
||||||
|
if (parentEdge != null) {
|
||||||
|
// The 'from' field of the edge gives us the ID of the parent node.
|
||||||
|
// We find that node in the skill tree nodes list.
|
||||||
|
currentNode = _skillTreeNodes
|
||||||
|
.firstWhereOrNull((node) => node.id.toString() == parentEdge.from);
|
||||||
|
} else {
|
||||||
|
// If no parent edge is found, it means we've reached the root node.
|
||||||
|
// We set currentNode to null to exit the loop.
|
||||||
|
currentNode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Getter to expose nodes for the View
|
// Getter to expose nodes for the View
|
||||||
List<SkillTreeNode> get skillTreeNodes => _skillTreeNodes;
|
List<SkillTreeNode> get skillTreeNodes => _skillTreeNodes;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:auto_gpt_flutter_client/views/side_bar/side_bar_view.dart';
|
|||||||
import 'package:auto_gpt_flutter_client/views/skill_tree/skill_tree_view.dart';
|
import 'package:auto_gpt_flutter_client/views/skill_tree/skill_tree_view.dart';
|
||||||
import 'package:auto_gpt_flutter_client/views/task/task_view.dart';
|
import 'package:auto_gpt_flutter_client/views/task/task_view.dart';
|
||||||
import 'package:auto_gpt_flutter_client/views/chat/chat_view.dart';
|
import 'package:auto_gpt_flutter_client/views/chat/chat_view.dart';
|
||||||
|
import 'package:auto_gpt_flutter_client/views/task_queue/task_queue_view.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@@ -18,37 +19,80 @@ class MainLayout extends StatelessWidget {
|
|||||||
// Get the screen width
|
// Get the screen width
|
||||||
double width = MediaQuery.of(context).size.width;
|
double width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
// Access the TaskViewModel from the context
|
// Access the various ViewModels from the context
|
||||||
final taskViewModel = Provider.of<TaskViewModel>(context);
|
final taskViewModel = Provider.of<TaskViewModel>(context);
|
||||||
|
|
||||||
// Access the ChatViewModel from the context
|
|
||||||
final chatViewModel = Provider.of<ChatViewModel>(context);
|
final chatViewModel = Provider.of<ChatViewModel>(context);
|
||||||
|
|
||||||
// Access the ChatViewModel from the context
|
// Initialize the width for the SideBarView
|
||||||
final skillTreeViewModel = Provider.of<SkillTreeViewModel>(context);
|
double sideBarWidth = 60.0;
|
||||||
|
|
||||||
|
// Initialize the width for the TaskView
|
||||||
|
double taskViewWidth = 280.0;
|
||||||
|
|
||||||
|
// Calculate remaining width after subtracting the width of the SideBarView
|
||||||
|
double remainingWidth = width - sideBarWidth;
|
||||||
|
|
||||||
|
// Declare variables to hold the widths of SkillTreeView, TestQueueView, and ChatView
|
||||||
|
double skillTreeViewWidth = 0;
|
||||||
|
double testQueueViewWidth = 0;
|
||||||
|
double chatViewWidth = 0;
|
||||||
|
|
||||||
// Check the screen width and return the appropriate layout
|
|
||||||
if (width > 800) {
|
if (width > 800) {
|
||||||
// For larger screens, return a side-by-side layout
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
SideBarView(selectedViewNotifier: selectedViewNotifier),
|
SizedBox(
|
||||||
|
width: sideBarWidth,
|
||||||
|
child: SideBarView(selectedViewNotifier: selectedViewNotifier)),
|
||||||
ValueListenableBuilder(
|
ValueListenableBuilder(
|
||||||
valueListenable: selectedViewNotifier,
|
valueListenable: selectedViewNotifier,
|
||||||
builder: (context, String value, _) {
|
builder: (context, String value, _) {
|
||||||
|
return Consumer<SkillTreeViewModel>(
|
||||||
|
builder: (context, skillTreeViewModel, _) {
|
||||||
if (value == 'TaskView') {
|
if (value == 'TaskView') {
|
||||||
return SizedBox(
|
skillTreeViewModel.resetState();
|
||||||
width: 280, child: TaskView(viewModel: taskViewModel));
|
chatViewWidth = remainingWidth - taskViewWidth;
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: taskViewWidth,
|
||||||
|
child: TaskView(viewModel: taskViewModel)),
|
||||||
|
SizedBox(
|
||||||
|
width: chatViewWidth,
|
||||||
|
child: ChatView(viewModel: chatViewModel))
|
||||||
|
],
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return Expanded(
|
if (skillTreeViewModel.selectedNode != null) {
|
||||||
child: SkillTreeView(viewModel: skillTreeViewModel));
|
// If TaskQueueView should be displayed
|
||||||
|
testQueueViewWidth = remainingWidth * 0.25;
|
||||||
|
skillTreeViewWidth = remainingWidth * 0.25;
|
||||||
|
chatViewWidth = remainingWidth * 0.5;
|
||||||
|
} else {
|
||||||
|
// If only SkillTreeView and ChatView should be displayed
|
||||||
|
skillTreeViewWidth = remainingWidth * 0.5;
|
||||||
|
chatViewWidth = remainingWidth * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: skillTreeViewWidth,
|
||||||
|
child:
|
||||||
|
SkillTreeView(viewModel: skillTreeViewModel)),
|
||||||
|
if (skillTreeViewModel.selectedNode != null)
|
||||||
|
SizedBox(
|
||||||
|
width: testQueueViewWidth,
|
||||||
|
child: TaskQueueView()),
|
||||||
|
SizedBox(
|
||||||
|
width: chatViewWidth,
|
||||||
|
child: ChatView(viewModel: chatViewModel)),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Expanded(
|
|
||||||
child: ChatView(
|
|
||||||
viewModel: chatViewModel,
|
|
||||||
)),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
71
frontend/lib/views/task_queue/task_queue_view.dart
Normal file
71
frontend/lib/views/task_queue/task_queue_view.dart
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:auto_gpt_flutter_client/viewmodels/skill_tree_viewmodel.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
// TODO: Add view model for task queue instead of skill tree view model
|
||||||
|
class TaskQueueView extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final viewModel = Provider.of<SkillTreeViewModel>(context);
|
||||||
|
|
||||||
|
// Reverse the node hierarchy
|
||||||
|
final reversedHierarchy =
|
||||||
|
viewModel.selectedNodeHierarchy?.reversed.toList() ?? [];
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
color: Colors.white,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// The list of tasks (tiles)
|
||||||
|
ListView.builder(
|
||||||
|
itemCount: reversedHierarchy.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final node = reversedHierarchy[index];
|
||||||
|
return Container(
|
||||||
|
margin: EdgeInsets.fromLTRB(20, 5, 20, 5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white, // white background
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.black, width: 1), // thin black border
|
||||||
|
borderRadius: BorderRadius.circular(4), // small corner radius
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
title: Center(child: Text('Node ID: ${node.id}')),
|
||||||
|
subtitle: Center(child: Text('Color: ${node.color}')),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
// Checkmark button at the bottom right
|
||||||
|
Positioned(
|
||||||
|
bottom: 50,
|
||||||
|
right: 50,
|
||||||
|
child: Tooltip(
|
||||||
|
message: 'Run suite of tests',
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// Add your logic here to run the suite of tests
|
||||||
|
},
|
||||||
|
child: Icon(Icons.check, color: Colors.green),
|
||||||
|
style: ButtonStyle(
|
||||||
|
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: MaterialStateProperty.all(Colors.white),
|
||||||
|
side: MaterialStateProperty.all(
|
||||||
|
BorderSide(color: Colors.green, width: 3)),
|
||||||
|
minimumSize:
|
||||||
|
MaterialStateProperty.all(Size(50, 50)), // Square size
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.all(0)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user