mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
fix(frontend/backend): improve HITL review UX with proper naming and styling
Backend changes: - Use user-provided name directly as review message (not wrapped in 'Review required for X execution') - Pass input_data.name from HITL block instead of generic block type name Frontend changes: - Show HITL block name in bold in collapse header - Use HITL name (not node UUID) in auto-approve text - Default all review groups to collapsed state - Remove redundant instructions display from individual cards - Fix lint error: remove unused instructions variable This ensures HITL blocks show meaningful names like 'User profile data' instead of UUIDs or generic 'HumanInTheLoopBlock' in the UI.
This commit is contained in:
@@ -134,7 +134,7 @@ class HITLReviewHelper:
|
||||
graph_id=graph_id,
|
||||
graph_version=graph_version,
|
||||
input_data=input_data,
|
||||
message=f"Review required for {block_name} execution",
|
||||
message=block_name, # Use block_name directly as the message
|
||||
editable=editable,
|
||||
)
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ class HumanInTheLoopBlock(Block):
|
||||
graph_exec_id=graph_exec_id,
|
||||
graph_id=graph_id,
|
||||
graph_version=graph_version,
|
||||
block_name=self.name,
|
||||
block_name=input_data.name, # Use user-provided name instead of block type
|
||||
editable=input_data.editable,
|
||||
)
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ export function PendingReviewCard({
|
||||
}: PendingReviewCardProps) {
|
||||
const extractedData = extractReviewData(review.payload);
|
||||
const isDataEditable = review.editable;
|
||||
const instructions = extractedData.instructions || review.instructions;
|
||||
const [currentData, setCurrentData] = useState(extractedData.data);
|
||||
|
||||
// Sync with external data value when auto-approve is toggled
|
||||
@@ -142,13 +141,6 @@ export function PendingReviewCard({
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to get proper field label
|
||||
const getFieldLabel = (instructions?: string) => {
|
||||
if (instructions)
|
||||
return instructions.charAt(0).toUpperCase() + instructions.slice(1);
|
||||
return "Data to Review";
|
||||
};
|
||||
|
||||
// Helper function to shorten node ID
|
||||
const getShortenedNodeId = (id: string) => {
|
||||
if (id.length <= 8) return id;
|
||||
@@ -165,46 +157,18 @@ export function PendingReviewCard({
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* Show instructions as field label */}
|
||||
{instructions && (
|
||||
<div className="space-y-3">
|
||||
<Text variant="body" className="font-semibold text-gray-900">
|
||||
{getFieldLabel(instructions)}
|
||||
</Text>
|
||||
{isDataEditable && !autoApproveFuture ? (
|
||||
renderDataInput()
|
||||
) : (
|
||||
<div className="rounded-lg border border-gray-200 bg-white p-3">
|
||||
<Text variant="small" className="text-gray-600">
|
||||
{JSON.stringify(currentData, null, 2)}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* If no instructions, show data directly */}
|
||||
{!instructions && (
|
||||
<div className="space-y-3">
|
||||
<Text variant="body" className="font-semibold text-gray-900">
|
||||
Data to Review
|
||||
{!isDataEditable && (
|
||||
<span className="ml-2 text-xs text-muted-foreground">
|
||||
(Read-only)
|
||||
</span>
|
||||
)}
|
||||
</Text>
|
||||
{isDataEditable && !autoApproveFuture ? (
|
||||
renderDataInput()
|
||||
) : (
|
||||
<div className="rounded-lg border border-gray-200 bg-white p-3">
|
||||
<Text variant="small" className="text-gray-600">
|
||||
{JSON.stringify(currentData, null, 2)}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* Show data input/display */}
|
||||
<div className="space-y-3">
|
||||
{isDataEditable && !autoApproveFuture ? (
|
||||
renderDataInput()
|
||||
) : (
|
||||
<div className="rounded-lg border border-gray-200 bg-white p-3">
|
||||
<Text variant="small" className="text-gray-600">
|
||||
{JSON.stringify(currentData, null, 2)}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Auto-approve toggle for this review */}
|
||||
{showAutoApprove && onAutoApproveFutureChange && (
|
||||
|
||||
@@ -274,12 +274,16 @@ export function PendingReviewsList({
|
||||
|
||||
<div className="space-y-7">
|
||||
{Object.entries(groupedReviews).map(([nodeId, nodeReviews]) => {
|
||||
const isCollapsed = collapsedGroups[nodeId];
|
||||
// Default to collapsed for all reviews
|
||||
const isCollapsed = collapsedGroups[nodeId] ?? true;
|
||||
const displayName =
|
||||
nodeNameMap[nodeId] || nodeReviews[0]?.node_id || "Unknown Block";
|
||||
const reviewCount = nodeReviews.length;
|
||||
const shouldShowCollapseHeader =
|
||||
Object.keys(groupedReviews).length > 1 && reviewCount > 1;
|
||||
|
||||
// Get review title from instructions field
|
||||
// Only show if it's a HITL block (has custom instructions)
|
||||
const firstReview = nodeReviews[0];
|
||||
const reviewTitle = firstReview?.instructions;
|
||||
|
||||
// Helper to shorten node ID
|
||||
const getShortenedNodeId = (id: string) => {
|
||||
@@ -289,30 +293,38 @@ export function PendingReviewsList({
|
||||
|
||||
return (
|
||||
<div key={nodeId} className="space-y-4">
|
||||
{/* Group Header - Only show if multiple groups AND multiple reviews in this group */}
|
||||
{shouldShowCollapseHeader && (
|
||||
<button
|
||||
onClick={() => toggleGroupCollapse(nodeId)}
|
||||
className="flex w-full items-center gap-2 text-left"
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<CaretRightIcon size={20} className="text-gray-600" />
|
||||
) : (
|
||||
<CaretDownIcon size={20} className="text-gray-600" />
|
||||
{/* Group Header - Always show for all groups */}
|
||||
<button
|
||||
onClick={() => toggleGroupCollapse(nodeId)}
|
||||
className="flex w-full items-center gap-2 text-left"
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<CaretRightIcon size={20} className="text-gray-600" />
|
||||
) : (
|
||||
<CaretDownIcon size={20} className="text-gray-600" />
|
||||
)}
|
||||
<div className="flex-1">
|
||||
{reviewTitle && (
|
||||
<Text
|
||||
variant="body"
|
||||
className="font-semibold text-gray-900"
|
||||
>
|
||||
{reviewTitle}
|
||||
</Text>
|
||||
)}
|
||||
<Text variant="body" className="flex-1 text-gray-900">
|
||||
<Text variant="small" className="text-gray-500">
|
||||
Node #{getShortenedNodeId(nodeId)}
|
||||
</Text>
|
||||
<span className="text-xs text-gray-600">
|
||||
{reviewCount} {reviewCount === 1 ? "review" : "reviews"}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs text-gray-600">
|
||||
{reviewCount} {reviewCount === 1 ? "review" : "reviews"}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* Reviews in this group */}
|
||||
{!isCollapsed && (
|
||||
<div className="space-y-4">
|
||||
{nodeReviews.map((review, index) => (
|
||||
{nodeReviews.map((review) => (
|
||||
<PendingReviewCard
|
||||
key={review.node_exec_id}
|
||||
review={review}
|
||||
@@ -320,7 +332,6 @@ export function PendingReviewsList({
|
||||
autoApproveFuture={autoApproveFutureMap[nodeId] || false}
|
||||
externalDataValue={reviewDataMap[review.node_exec_id]}
|
||||
showAutoApprove={false}
|
||||
nodeId={shouldShowCollapseHeader ? undefined : nodeId}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -334,14 +345,15 @@ export function PendingReviewsList({
|
||||
}
|
||||
/>
|
||||
<Text variant="small" className="text-gray-700">
|
||||
Auto-approve future executions of {displayName}
|
||||
Auto-approve future executions of{" "}
|
||||
{reviewTitle || `Node #${getShortenedNodeId(nodeId)}`}
|
||||
</Text>
|
||||
</div>
|
||||
{autoApproveFutureMap[nodeId] && (
|
||||
<Text variant="small" className="pl-11 text-gray-500">
|
||||
Original data will be used for all {reviewCount}{" "}
|
||||
{reviewCount === 1 ? "review" : "reviews"} from{" "}
|
||||
{displayName} in future executions.
|
||||
{reviewCount === 1 ? "review" : "reviews"} in future
|
||||
executions.
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user