Skip to content

Commit da4e479

Browse files
authored
πŸ› An agent with the same name should be displayed as unusable.
2 parents 96227fb + 75cc4e5 commit da4e479

File tree

6 files changed

+226
-100
lines changed

6 files changed

+226
-100
lines changed

β€Žfrontend/app/[locale]/chat/components/chatAgentSelector.tsxβ€Ž

Lines changed: 151 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"use client";
22

3-
import React, { useState, useEffect, useRef } from "react";
3+
import React, { useState, useEffect, useRef, useMemo } from "react";
44
import { createPortal } from "react-dom";
55
import { useTranslation } from "react-i18next";
6-
import { ChevronDown, MousePointerClick } from "lucide-react";
6+
import { ChevronDown, MousePointerClick, AlertCircle } from "lucide-react";
77

88
import { fetchAllAgents } from "@/services/agentConfigService";
99
import { getUrlParam } from "@/lib/utils";
@@ -34,6 +34,39 @@ export function ChatAgentSelector({
3434
(agent) => agent.agent_id === selectedAgentId
3535
);
3636

37+
// Detect duplicate agent names and mark later-added agents as disabled
38+
// For agents with the same name, keep the first one (smallest ID) enabled, disable the rest
39+
const duplicateAgentInfo = useMemo(() => {
40+
// Create a map to track agents by name
41+
const nameToAgents = new Map<string, Agent[]>();
42+
43+
agents.forEach((agent) => {
44+
const agentName = agent.name;
45+
if (!nameToAgents.has(agentName)) {
46+
nameToAgents.set(agentName, []);
47+
}
48+
nameToAgents.get(agentName)!.push(agent);
49+
});
50+
51+
// For each group of agents with the same name, sort by ID (smallest first)
52+
// Mark all except the first one as disabled
53+
const disabledAgentIds = new Set<number>();
54+
55+
nameToAgents.forEach((agents, name) => {
56+
if (agents.length > 1) {
57+
// Sort by agent_id (smallest first)
58+
const sortedAgents = [...agents].sort((a, b) => a.agent_id - b.agent_id);
59+
60+
// Mark all except the first one as disabled
61+
for (let i = 1; i < sortedAgents.length; i++) {
62+
disabledAgentIds.add(sortedAgents[i].agent_id);
63+
}
64+
}
65+
});
66+
67+
return { disabledAgentIds, nameToAgents };
68+
}, [agents]);
69+
3770
/**
3871
* Handle URL parameter auto-selection logic for Agent
3972
*/
@@ -46,11 +79,17 @@ export function ChatAgentSelector({
4679
);
4780
if (agentId === null) return;
4881

49-
// Check if agentId is a valid agent
82+
// Check if agentId is a valid and effectively available agent
5083
const agent = agents.find((a) => a.agent_id === agentId);
51-
if (agent && agent.is_available) {
52-
handleAgentSelect(agentId);
53-
setIsAutoSelectInit(true);
84+
if (agent) {
85+
const isAvailableTool = agent.is_available !== false;
86+
const isDuplicateDisabled = duplicateAgentInfo.disabledAgentIds.has(agent.agent_id);
87+
const isEffectivelyAvailable = isAvailableTool && !isDuplicateDisabled;
88+
89+
if (isEffectivelyAvailable) {
90+
handleAgentSelect(agentId);
91+
setIsAutoSelectInit(true);
92+
}
5493
}
5594
};
5695

@@ -61,7 +100,8 @@ export function ChatAgentSelector({
61100
// Execute auto-selection logic when agents are loaded
62101
useEffect(() => {
63102
handleAutoSelectAgent();
64-
}, [agents]);
103+
// eslint-disable-next-line react-hooks/exhaustive-deps
104+
}, [agents, duplicateAgentInfo]);
65105

66106
// Calculate dropdown position
67107
useEffect(() => {
@@ -157,11 +197,17 @@ export function ChatAgentSelector({
157197
};
158198

159199
const handleAgentSelect = (agentId: number | null) => {
160-
// Only available agents can be selected
200+
// Only effectively available agents can be selected
161201
if (agentId !== null) {
162202
const agent = agents.find((a) => a.agent_id === agentId);
163-
if (agent && !agent.is_available) {
164-
return; // Unavailable agents cannot be selected
203+
if (agent) {
204+
const isAvailableTool = agent.is_available !== false;
205+
const isDuplicateDisabled = duplicateAgentInfo.disabledAgentIds.has(agent.agent_id);
206+
const isEffectivelyAvailable = isAvailableTool && !isDuplicateDisabled;
207+
208+
if (!isEffectivelyAvailable) {
209+
return; // Unavailable agents cannot be selected
210+
}
165211
}
166212
}
167213

@@ -301,90 +347,108 @@ export function ChatAgentSelector({
301347
)}
302348
</div>
303349
) : (
304-
allAgents.map((agent, idx) => (
305-
<div
306-
key={agent.agent_id}
307-
className={`
308-
flex items-start gap-3 px-3.5 py-2.5 text-sm
309-
transition-all duration-150 ease-in-out
310-
${
311-
agent.is_available
312-
? `hover:bg-slate-50 cursor-pointer ${
313-
selectedAgentId === agent.agent_id
314-
? "bg-blue-50/70 text-blue-600 hover:bg-blue-50/70"
315-
: ""
316-
}`
317-
: "cursor-not-allowed bg-slate-50/50"
318-
}
319-
${
320-
selectedAgentId === agent.agent_id
321-
? "shadow-[inset_2px_0_0_0] shadow-blue-500"
322-
: ""
350+
allAgents.map((agent, idx) => {
351+
const isAvailableTool = agent.is_available !== false;
352+
const isDuplicateDisabled = duplicateAgentInfo.disabledAgentIds.has(agent.agent_id);
353+
const isEffectivelyAvailable = isAvailableTool && !isDuplicateDisabled;
354+
355+
// Determine the reason for unavailability
356+
let unavailableReason: string | null = null;
357+
if (!isEffectivelyAvailable) {
358+
if (isDuplicateDisabled) {
359+
unavailableReason = t("subAgentPool.tooltip.duplicateNameDisabled");
360+
} else if (!isAvailableTool) {
361+
unavailableReason = t("subAgentPool.tooltip.hasUnavailableTools");
323362
}
324-
${idx !== 0 ? "border-t border-slate-100" : ""}
325-
`}
326-
onClick={() =>
327-
agent.is_available && handleAgentSelect(agent.agent_id)
328-
}
329-
>
330-
{/* Agent Icon */}
331-
<div className="flex-shrink-0 mt-0.5">
332-
<MousePointerClick
333-
className={`h-4 w-4 ${
334-
agent.is_available
335-
? selectedAgentId === agent.agent_id
336-
? "text-blue-500"
337-
: "text-slate-500"
338-
: "text-slate-300"
339-
}`}
340-
/>
341-
</div>
363+
}
364+
365+
return (
366+
<div
367+
key={agent.agent_id}
368+
className={`
369+
flex items-start gap-3 px-3.5 py-2.5 text-sm
370+
transition-all duration-150 ease-in-out
371+
${
372+
isEffectivelyAvailable
373+
? `hover:bg-slate-50 cursor-pointer ${
374+
selectedAgentId === agent.agent_id
375+
? "bg-blue-50/70 text-blue-600 hover:bg-blue-50/70"
376+
: ""
377+
}`
378+
: "opacity-60 cursor-not-allowed bg-slate-50/50"
379+
}
380+
${
381+
selectedAgentId === agent.agent_id
382+
? "shadow-[inset_2px_0_0_0] shadow-blue-500"
383+
: ""
384+
}
385+
${idx !== 0 ? "border-t border-slate-100" : ""}
386+
`}
387+
onClick={() =>
388+
isEffectivelyAvailable && handleAgentSelect(agent.agent_id)
389+
}
390+
>
391+
{/* Agent Icon */}
392+
<div className="flex-shrink-0 mt-0.5">
393+
{isEffectivelyAvailable ? (
394+
<MousePointerClick
395+
className={`h-4 w-4 ${
396+
selectedAgentId === agent.agent_id
397+
? "text-blue-500"
398+
: "text-slate-500"
399+
}`}
400+
/>
401+
) : (
402+
<AlertCircle className="h-4 w-4 text-amber-500" />
403+
)}
404+
</div>
342405

343-
{/* Agent Info */}
344-
<div className="flex-1 min-w-0">
345-
<div
346-
className={`font-medium truncate ${
347-
agent.is_available
348-
? selectedAgentId === agent.agent_id
349-
? "text-blue-600"
350-
: "text-slate-700 hover:text-slate-900"
351-
: "text-slate-400"
352-
}`}
353-
>
354-
<div className="flex items-center">
355-
{agent.display_name && (
356-
<span className="text-sm leading-none">
357-
{agent.display_name}
406+
{/* Agent Info */}
407+
<div className="flex-1 min-w-0">
408+
<div
409+
className={`font-medium truncate ${
410+
isEffectivelyAvailable
411+
? selectedAgentId === agent.agent_id
412+
? "text-blue-600"
413+
: "text-slate-700 hover:text-slate-900"
414+
: "text-slate-400"
415+
}`}
416+
>
417+
<div className="flex items-center gap-1.5">
418+
{agent.display_name && (
419+
<span className="text-sm leading-none">
420+
{agent.display_name}
421+
</span>
422+
)}
423+
<span
424+
className={`text-sm leading-none align-baseline ${
425+
agent.display_name ? "ml-2" : "text-sm"
426+
}`}
427+
>
428+
{agent.name}
429+
</span>
430+
</div>
431+
</div>
432+
<div
433+
className={`text-xs mt-1 leading-relaxed ${
434+
isEffectivelyAvailable
435+
? selectedAgentId === agent.agent_id
436+
? "text-blue-500"
437+
: "text-slate-500"
438+
: "text-slate-400"
439+
}`}
440+
>
441+
{agent.description}
442+
{unavailableReason && (
443+
<span className="block mt-1.5 text-amber-600 font-medium">
444+
{unavailableReason}
358445
</span>
359446
)}
360-
<span
361-
className={`text-sm leading-none align-baseline ${
362-
agent.display_name ? "ml-2" : "text-sm"
363-
}`}
364-
>
365-
{agent.name}
366-
</span>
367447
</div>
368448
</div>
369-
<div
370-
className={`text-xs mt-1 leading-relaxed ${
371-
agent.is_available
372-
? selectedAgentId === agent.agent_id
373-
? "text-blue-500"
374-
: "text-slate-500"
375-
: "text-slate-300"
376-
}`}
377-
>
378-
{agent.description}
379-
{!agent.is_available && (
380-
<span className="block mt-1 text-red-400">
381-
{t("agentSelector.agentUnavailable")}
382-
</span>
383-
)}
384-
</div>
385449
</div>
386-
</div>
387-
))
450+
);
451+
})
388452
)}
389453
</div>
390454
</div>

β€Žfrontend/app/[locale]/setup/agents/components/AgentSetupOrchestrator.tsxβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,7 @@ export default function AgentSetupOrchestrator({
938938
isGeneratingAgent={isGeneratingAgent}
939939
editingAgent={editingAgent}
940940
isCreatingNewAgent={isCreatingNewAgent}
941+
editingAgentName={agentName || null}
941942
/>
942943
</div>
943944

0 commit comments

Comments
Β (0)