Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions frontend/app/[locale]/agents/components/AgentManageComp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,16 @@ export default function AgentManageComp() {
return;
}

// Clear NEW mark when agent is selected for editing
try {
const res = await clearAgentAndSync(agent.id, queryClient);
if (!res?.success) {
log.warn("Failed to clear NEW mark on select:", res);
// Clear NEW mark when agent is selected for editing (only if marked as new)
if (agent.is_new) {
try {
const res = await clearAgentAndSync(agent.id, queryClient);
if (!res?.success) {
log.warn("Failed to clear NEW mark on select:", res);
}
} catch (err) {
log.error("Failed to clear NEW mark on select:", err);
}
} catch (err) {
log.error("Failed to clear NEW mark on select:", err);
}

// Set selected agent id to trigger the hook
Expand Down
64 changes: 38 additions & 26 deletions frontend/app/[locale]/agents/components/agentManage/AgentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,37 @@ export default function AgentList({
const confirm = useConfirmModal();
const queryClient = useQueryClient();

// Note: rely on agent.is_new from agentList (single source of truth).
// Clear NEW mark when agent is selected (sync with selection visual feedback)
useEffect(() => {
if (currentAgentId) {
const agentId = String(currentAgentId);
const agent = agentList.find(a => String(a.id) === agentId);
if (agent?.is_new) {
(async () => {
try {
const res = await clearAgentAndSync(agentId, queryClient);
if (!res?.success) {
log.warn("Failed to clear NEW mark for agent:", agentId, res);
}
} catch (err) {
log.error("Error clearing NEW mark:", err);
}
})();
// Local selected agent ID for optimistic UI update (immediate visual feedback)
const [locallySelectedAgentId, setLocallySelectedAgentId] = useState<string | null>(null);

// Track cleared NEW marks to avoid redundant API calls and enable optimistic UI update
const clearedNewMarkIds = React.useRef<Set<string>>(new Set());

// Handle agent selection and clear NEW mark if needed
const handleAgentSelect = async (agent: Agent) => {
// Optimistic update: immediately set selected state for UI
setLocallySelectedAgentId(String(agent.id));

// Optimistic update: immediately mark as cleared for NEW label
if (agent.is_new === true) {
clearedNewMarkIds.current.add(String(agent.id));
}

// Only clear NEW mark if agent is marked as new and hasn't been cleared yet
if (agent.is_new === true && !clearedNewMarkIds.current.has(String(agent.id))) {
try {
const res = await clearAgentAndSync(String(agent.id), queryClient);
if (res?.success) {
clearedNewMarkIds.current.add(String(agent.id));
} else {
log.warn("Failed to clear NEW mark for agent:", agent.id, res);
}
} catch (err) {
log.error("Error clearing NEW mark:", err);
}
}
}, [currentAgentId, agentList]);
onSelectAgent(agent);
};

// Call relationship modal state
const [callRelationshipModalVisible, setCallRelationshipModalVisible] =
Expand Down Expand Up @@ -292,8 +303,9 @@ export default function AgentList({
? "opacity-60 cursor-not-allowed"
: "hover:bg-gray-50 cursor-pointer"
} ${
currentAgentId !== null &&
String(currentAgentId) === String(agent.id)
// Use local state for immediate visual feedback
locallySelectedAgentId !== null &&
String(locallySelectedAgentId) === String(agent.id)
? "bg-blue-50 selected-row pl-3"
: ""
}`;
Expand All @@ -302,9 +314,7 @@ export default function AgentList({
onClick: (e: any) => {
e.preventDefault();
e.stopPropagation();

// Call onSelectAgent - NEW mark clearing is handled by useEffect
onSelectAgent(agent);
handleAgentSelect(agent);
},
})}
columns={[
Expand All @@ -314,10 +324,12 @@ export default function AgentList({
const isAvailable = agent.is_available !== false;
const displayName = agent.display_name || "";
const name = agent.name || "";
// Use local state for immediate visual feedback
const isSelected =
currentAgentId !== null &&
String(currentAgentId) === String(agent.id);
const isNew = agent.is_new || false;
locallySelectedAgentId !== null &&
String(locallySelectedAgentId) === String(agent.id);
// Optimistic update: use clearedNewMarkIds to hide NEW immediately after click
const isNew = (agent.is_new || false) && !clearedNewMarkIds.current.has(String(agent.id));

return (
<Flex
Expand Down
30 changes: 16 additions & 14 deletions frontend/app/[locale]/chat/components/chatAgentSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,21 +215,23 @@ export function ChatAgentSelector({
return; // Unavailable agents cannot be selected
}

// Clear NEW mark when agent is selected for chat
try {
const res = await clearAgentAndSync(agentId, queryClient);
if (res?.success) {
// update local agents state to reflect cleared NEW mark immediately
setAgents((prev) =>
prev.map((a) =>
a.agent_id === agentId ? { ...a, is_new: false } : a
)
);
} else {
log.warn("Failed to clear NEW mark on select:", res);
// Clear NEW mark when agent is selected for chat (only if marked as new)
if (agent.is_new === true) {
try {
const res = await clearAgentAndSync(agentId, queryClient);
if (res?.success) {
// update local agents state to reflect cleared NEW mark immediately
setAgents((prev) =>
prev.map((a) =>
a.agent_id === agentId ? { ...a, is_new: false } : a
)
);
} else {
log.warn("Failed to clear NEW mark on select:", res);
}
} catch (e) {
log.error("Failed to clear NEW mark on select:", e);
}
} catch (e) {
log.error("Failed to clear NEW mark on select:", e);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions frontend/types/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface Agent {
display_name: string;
description: string;
is_available: boolean;
is_new?: boolean;
}

export interface ChatAgentSelectorProps {
Expand Down
Loading