Skip to content

Commit e2b16f3

Browse files
committed
Centralize agent icon and display name logic
Refactored agent icon and display name selection into a new utility (agentIconUtils.tsx) for consistent usage across components. Updated PlanPanelRight, StreamingAgentMessage, StreamingPlanResponse, and HomeInput to use the new utilities, improving maintainability and ensuring agents have deterministic icons and formatted names. Removed legacy and duplicate icon logic from components.
2 parents 9420811 + 3210725 commit e2b16f3

File tree

6 files changed

+486
-222
lines changed

6 files changed

+486
-222
lines changed

src/frontend/src/components/content/HomeInput.tsx

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,25 @@ const getIconFromString = (iconString: string | React.ReactNode): React.ReactNod
3535

3636
const truncateDescription = (description: string, maxLength: number = 180): string => {
3737
if (!description) return '';
38-
38+
3939
if (description.length <= maxLength) {
4040
return description;
4141
}
42+
4243

43-
// Find the last space before the limit to avoid cutting words
4444
const truncated = description.substring(0, maxLength);
4545
const lastSpaceIndex = truncated.lastIndexOf(' ');
46-
47-
// If there's a space within the last 20 characters, cut there
48-
// Otherwise, cut at the exact limit
46+
4947
const cutPoint = lastSpaceIndex > maxLength - 20 ? lastSpaceIndex : maxLength;
50-
48+
5149
return description.substring(0, cutPoint) + '...';
5250
};
5351

52+
// Extended QuickTask interface to store both truncated and full descriptions
53+
interface ExtendedQuickTask extends QuickTask {
54+
fullDescription: string; // Store the full, untruncated description
55+
}
56+
5457
const HomeInput: React.FC<HomeInputProps> = ({
5558
selectedTeam,
5659
}) => {
@@ -126,10 +129,10 @@ const HomeInput: React.FC<HomeInputProps> = ({
126129
errorDetail = error?.response?.data?.detail;
127130
}
128131

129-
// Handle RAI validation errors - just show description as toast
132+
// Handle RAI validation errors - just show description as toast
130133
if (errorDetail?.error_type === 'RAI_VALIDATION_FAILED') {
131-
const description = errorDetail.description ||
132-
"Your request contains content that doesn't meet our safety guidelines. Please try rephrasing.";
134+
const description = errorDetail.description ||
135+
"Your request contains content that doesn't meet our safety guidelines. Please try rephrasing.";
133136
showToast(description, "error");
134137
} else {
135138
// Handle other errors with toast messages
@@ -138,7 +141,7 @@ const HomeInput: React.FC<HomeInputProps> = ({
138141
error?.response?.data?.message ||
139142
error?.message ||
140143
"Something went wrong. Please try again.";
141-
144+
142145
showToast(errorMessage, "error");
143146
}
144147
} finally {
@@ -148,13 +151,12 @@ const HomeInput: React.FC<HomeInputProps> = ({
148151
}
149152
};
150153

151-
const handleQuickTaskClick = (task: QuickTask) => {
152-
setInput(task.description);
154+
const handleQuickTaskClick = (task: ExtendedQuickTask) => {
155+
setInput(task.fullDescription);
153156
setRAIError(null); // Clear any RAI errors when selecting a quick task
154157
if (textareaRef.current) {
155158
textareaRef.current.focus();
156159
}
157-
158160
};
159161

160162
useEffect(() => {
@@ -164,15 +166,16 @@ const HomeInput: React.FC<HomeInputProps> = ({
164166
}
165167
}, [input]);
166168

167-
// Convert team starting_tasks to QuickTask format
168-
const tasksToDisplay: QuickTask[] = selectedTeam && selectedTeam.starting_tasks ?
169+
// Convert team starting_tasks to ExtendedQuickTask format
170+
const tasksToDisplay: ExtendedQuickTask[] = selectedTeam && selectedTeam.starting_tasks ?
169171
selectedTeam.starting_tasks.map((task, index) => {
170172
// Handle both string tasks and StartingTask objects
171173
if (typeof task === 'string') {
172174
return {
173175
id: `team-task-${index}`,
174176
title: task,
175177
description: truncateDescription(task),
178+
fullDescription: task, // Store the full description
176179
icon: getIconFromString("📋")
177180
};
178181
} else {
@@ -183,6 +186,7 @@ const HomeInput: React.FC<HomeInputProps> = ({
183186
id: startingTask.id || `team-task-${index}`,
184187
title: startingTask.name || startingTask.prompt || 'Task',
185188
description: truncateDescription(taskDescription),
189+
fullDescription: taskDescription, // Store the full description
186190
icon: getIconFromString(startingTask.logo || "📋")
187191
};
188192
}
@@ -196,6 +200,20 @@ const HomeInput: React.FC<HomeInputProps> = ({
196200
<Title2>How can I help?</Title2>
197201
</div>
198202

203+
{/* Show RAI error if present */}
204+
{/* {raiError && (
205+
<RAIErrorCard
206+
error={raiError}
207+
onRetry={() => {
208+
setRAIError(null);
209+
if (textareaRef.current) {
210+
textareaRef.current.focus();
211+
}
212+
}}
213+
onDismiss={() => setRAIError(null)}
214+
/>
215+
)} */}
216+
199217
<ChatInput
200218
ref={textareaRef} // forwarding
201219
value={input}

src/frontend/src/components/content/PlanPanelRight.tsx

Lines changed: 55 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ import {
33
Body1,
44
} from "@fluentui/react-components";
55
import {
6-
PersonRegular,
76
ArrowTurnDownRightRegular,
87
} from "@fluentui/react-icons";
98
import { MPlanData, PlanDetailsProps } from "../../models";
10-
import { TaskService } from "../../services/TaskService";
11-
import { AgentTypeUtils, AgentType } from "../../models/enums";
9+
import { getAgentIcon, getAgentDisplayNameWithSuffix } from '../../utils/agentIconUtils';
1210
import ContentNotFound from "../NotFound/ContentNotFound";
1311

1412

@@ -18,121 +16,82 @@ const PlanPanelRight: React.FC<PlanDetailsProps> = ({
1816
planApprovalRequest
1917
}) => {
2018

21-
// Helper function to get clean agent display name
22-
const getAgentDisplayName = (agentName: string): string => {
23-
if (!agentName) return 'Assistant';
24-
25-
let cleanName = TaskService.cleanTextToSpaces(agentName);
26-
if (cleanName.toLowerCase().includes('agent')) {
27-
cleanName = cleanName.replace(/agent/gi, '').trim();
28-
}
29-
return cleanName.replace(/\b\w/g, l => l.toUpperCase()) || 'Assistant';
30-
};
31-
32-
// Helper function to get agent icon based on name and type
33-
const getAgentIcon = (agentName: string) => {
34-
// Try to determine agent type from name
35-
const cleanName = agentName.toLowerCase();
36-
let agentType: AgentType;
37-
38-
if (cleanName.includes('coder')) {
39-
agentType = AgentType.CODER;
40-
} else if (cleanName.includes('executor')) {
41-
agentType = AgentType.EXECUTOR;
42-
} else if (cleanName.includes('filesurfer')) {
43-
agentType = AgentType.FILE_SURFER;
44-
} else if (cleanName.includes('websurfer')) {
45-
agentType = AgentType.WEB_SURFER;
46-
} else if (cleanName.includes('hr')) {
47-
agentType = AgentType.HR;
48-
} else if (cleanName.includes('marketing')) {
49-
agentType = AgentType.MARKETING;
50-
} else if (cleanName.includes('procurement')) {
51-
agentType = AgentType.PROCUREMENT;
52-
} else if (cleanName.includes('proxy')) {
53-
agentType = AgentType.GENERIC;
54-
} else {
55-
agentType = AgentType.GENERIC;
56-
}
57-
58-
// Get the icon name from the utility
59-
const iconName = AgentTypeUtils.getAgentIcon(agentType);
60-
61-
// Return the appropriate icon component or fallback to PersonRegular
62-
return <PersonRegular style={{
63-
color: 'var(--colorPaletteBlueForeground2)',
64-
fontSize: '16px'
65-
}} />;
66-
};
67-
6819
if (!planData && !loading) {
6920
return <ContentNotFound subtitle="The requested page could not be found." />;
7021
}
7122

7223
if (!planApprovalRequest) {
73-
return null;
24+
return (
25+
<div style={{
26+
width: '280px',
27+
height: '100vh',
28+
padding: '20px',
29+
display: 'flex',
30+
alignItems: 'center',
31+
justifyContent: 'center',
32+
borderLeft: '1px solid var(--colorNeutralStroke1)',
33+
color: 'var(--colorNeutralForeground3)',
34+
fontSize: '14px',
35+
fontStyle: 'italic'
36+
}}>
37+
No plan available
38+
</div>
39+
);
7440
}
7541

76-
// Parse plan steps - items ending with colons are headings, others are substeps
77-
const parsePlanSteps = () => {
78-
if (!planApprovalRequest.steps || planApprovalRequest.steps.length === 0) return [];
79-
80-
const result: Array<{ type: 'heading' | 'substep'; text: string }> = [];
81-
82-
planApprovalRequest.steps.forEach(step => {
83-
const action = step.cleanAction || step.action || '';
84-
const trimmedAction = action.trim();
85-
86-
if (trimmedAction) {
87-
// Check if the step ends with a colon
88-
if (trimmedAction.endsWith(':')) {
89-
// This is a heading
90-
result.push({ type: 'heading', text: trimmedAction });
91-
} else {
92-
// This is a substep
93-
result.push({ type: 'substep', text: trimmedAction });
94-
}
95-
}
96-
});
42+
// Extract plan steps from the planApprovalRequest
43+
const extractPlanSteps = () => {
44+
if (!planApprovalRequest.steps || planApprovalRequest.steps.length === 0) {
45+
return [];
46+
}
9747

98-
return result;
48+
return planApprovalRequest.steps.map((step, index) => {
49+
const action = step.action || step.cleanAction || '';
50+
const isHeading = action.trim().endsWith(':');
51+
52+
return {
53+
text: action.trim(),
54+
isHeading,
55+
key: `${index}-${action.substring(0, 20)}`
56+
};
57+
}).filter(step => step.text.length > 0);
9958
};
10059

101-
// Render Plan Section with scrolling
60+
// Render Plan Section
10261
const renderPlanSection = () => {
103-
const parsedSteps = parsePlanSteps();
62+
const planSteps = extractPlanSteps();
10463

10564
return (
10665
<div style={{
66+
marginBottom: '24px',
10767
paddingBottom: '20px',
108-
marginBottom: '20px',
109-
borderBottom: '1px solid var(--colorNeutralStroke2)',
110-
maxHeight: '50vh',
111-
overflow: 'hidden',
112-
display: 'flex',
113-
flexDirection: 'column'
68+
borderBottom: '1px solid var(--colorNeutralStroke1)'
11469
}}>
11570
<Body1 style={{
11671
marginBottom: '16px',
117-
flexShrink: 0,
11872
fontSize: '14px',
11973
fontWeight: 600,
12074
color: 'var(--colorNeutralForeground1)'
12175
}}>
122-
Plan
76+
Plan Overview
12377
</Body1>
12478

125-
{/* Scrollable Plan Steps */}
126-
<div style={{
127-
flex: 1,
128-
overflow: 'auto',
129-
paddingRight: '8px'
130-
}}>
79+
{planSteps.length === 0 ? (
80+
<div style={{
81+
textAlign: 'center',
82+
color: 'var(--colorNeutralForeground3)',
83+
fontSize: '14px',
84+
fontStyle: 'italic',
85+
padding: '20px'
86+
}}>
87+
Plan is being generated...
88+
</div>
89+
) : (
13190
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
132-
{parsedSteps.map((step, index) => (
133-
<div key={index}>
134-
{step.type === 'heading' ? (
135-
// Heading - no arrow, just the text
91+
{planSteps.map((step, index) => (
92+
<div key={step.key} style={{ display: 'flex', flexDirection: 'column' }}>
93+
{step.isHeading ? (
94+
// Heading - larger text, bold
13695
<Body1 style={{
13796
fontSize: '14px',
13897
fontWeight: 600,
@@ -167,7 +126,7 @@ const PlanPanelRight: React.FC<PlanDetailsProps> = ({
167126
</div>
168127
))}
169128
</div>
170-
</div>
129+
)}
171130
</div>
172131
);
173132
};
@@ -220,7 +179,7 @@ const PlanPanelRight: React.FC<PlanDetailsProps> = ({
220179
justifyContent: 'center',
221180
flexShrink: 0
222181
}}>
223-
{getAgentIcon(agentName)}
182+
{getAgentIcon(agentName, planData, planApprovalRequest)}
224183
</div>
225184

226185
{/* Agent Info - just name */}
@@ -230,7 +189,7 @@ const PlanPanelRight: React.FC<PlanDetailsProps> = ({
230189
fontSize: '14px',
231190
color: 'var(--colorNeutralForeground1)'
232191
}}>
233-
{getAgentDisplayName(agentName)} Agent
192+
{getAgentDisplayNameWithSuffix(agentName)}
234193
</Body1>
235194
</div>
236195
</div>

0 commit comments

Comments
 (0)