Skip to content

Commit 0c2ff8e

Browse files
葛锐葛锐
authored andcommitted
feat(agent-new): add is_new DB column, API and frontend NEW badge/clear flow
1 parent 9c5f60e commit 0c2ff8e

15 files changed

Lines changed: 297 additions & 11 deletions

File tree

backend/apps/agent_app.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
list_all_agent_info_impl,
1818
run_agent_stream,
1919
stop_agent_tasks,
20-
get_agent_call_relationship_impl
20+
get_agent_call_relationship_impl,
21+
clear_agent_new_mark_impl
2122
)
2223
from utils.auth_utils import get_current_user_info, get_current_user_id
2324

@@ -148,6 +149,21 @@ async def import_agent_api(request: AgentImportRequest, authorization: Optional[
148149
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent import error.")
149150

150151

152+
@agent_config_router.put("/clear_new/{agent_id}")
153+
async def clear_agent_new_mark_api(agent_id: int, authorization: Optional[str] = Header(None)):
154+
"""
155+
Clear the NEW mark for an agent
156+
"""
157+
try:
158+
user_id, tenant_id, _ = get_current_user_info(authorization)
159+
affected_rows = await clear_agent_new_mark_impl(agent_id, tenant_id, user_id)
160+
return {"message": "Agent NEW mark cleared successfully", "affected_rows": affected_rows}
161+
except Exception as e:
162+
logger.error(f"Failed to clear agent NEW mark: {str(e)}")
163+
raise HTTPException(
164+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Failed to clear agent NEW mark.")
165+
166+
151167
@agent_config_router.post("/check_name")
152168
async def check_agent_name_batch_api(request: AgentNameBatchCheckRequest, authorization: Optional[str] = Header(None)):
153169
"""

backend/database/agent_db.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import logging
22
from typing import List
33

4+
from sqlalchemy import update
45
from database.client import get_db_session, as_dict, filter_property
56
from database.db_models import AgentInfo, ToolInstance, AgentRelation
7+
from sqlalchemy import update, bindparam
68

79
logger = logging.getLogger("agent_db")
810

@@ -68,6 +70,47 @@ def query_sub_agents_id_list(main_agent_id: int, tenant_id: str):
6870
return [relation.selected_agent_id for relation in relations]
6971

7072

73+
def clear_agent_new_mark(agent_id: int, tenant_id: str, user_id: str):
74+
"""
75+
Clear the NEW mark for an agent
76+
77+
Args:
78+
agent_id (int): Agent ID
79+
tenant_id (str): Tenant ID
80+
user_id (str): User ID (for audit purposes)
81+
"""
82+
with get_db_session() as session:
83+
result = session.execute(
84+
update(AgentInfo)
85+
.where(
86+
AgentInfo.agent_id == agent_id,
87+
AgentInfo.tenant_id == tenant_id,
88+
AgentInfo.delete_flag == 'N'
89+
)
90+
.values(is_new=False, updated_by=user_id)
91+
)
92+
# return number of rows affected
93+
return result.rowcount
94+
95+
96+
def mark_agents_as_new(agent_ids: list[int], tenant_id: str, user_id: str):
97+
"""
98+
Mark a list of agents as new (is_new = True)
99+
"""
100+
if not agent_ids:
101+
return
102+
with get_db_session() as session:
103+
session.execute(
104+
update(AgentInfo)
105+
.where(
106+
AgentInfo.agent_id.in_(agent_ids),
107+
AgentInfo.tenant_id == tenant_id,
108+
AgentInfo.delete_flag == 'N'
109+
)
110+
.values(is_new=True, updated_by=user_id)
111+
)
112+
113+
71114
def create_agent(agent_info, tenant_id: str, user_id: str):
72115
"""
73116
Create a new agent in the database.
@@ -82,6 +125,7 @@ def create_agent(agent_info, tenant_id: str, user_id: str):
82125
"tenant_id": tenant_id,
83126
"created_by": user_id,
84127
"updated_by": user_id,
128+
"is_new": True, # Mark new agents as new
85129
})
86130
with get_db_session() as session:
87131
new_agent = AgentInfo(**filter_property(info_with_metadata, AgentInfo))

backend/database/db_models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ class AgentInfo(TableBase):
222222
business_logic_model_name = Column(String(100), doc="Model name used for business logic prompt generation")
223223
business_logic_model_id = Column(Integer, doc="Model ID used for business logic prompt generation, foreign key reference to model_record_t.model_id")
224224
group_ids = Column(String, doc="Agent group IDs list")
225+
is_new = Column(Boolean, default=False, doc="Whether this agent is marked as new for the user")
225226

226227

227228
class ToolInstance(TableBase):

backend/services/agent_service.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,9 @@ async def import_agent_impl(
11141114
agent_stack.append(need_import_agent_id)
11151115
agent_stack.extend(managed_agents)
11161116

1117+
# Return the mapping of original IDs to new IDs
1118+
return mapping_agent_id
1119+
11171120

11181121
async def import_agent_by_agent_id(
11191122
import_agent_info: ExportAndImportAgentInfo,
@@ -1221,6 +1224,31 @@ def load_default_agents_json_file(default_agent_path):
12211224
return all_json_files
12221225

12231226

1227+
async def clear_agent_new_mark_impl(agent_id: int, tenant_id: str, user_id: str):
1228+
"""
1229+
Clear the NEW mark for an agent
1230+
1231+
Args:
1232+
agent_id (int): Agent ID
1233+
tenant_id (str): Tenant ID
1234+
user_id (str): User ID (for audit purposes)
1235+
"""
1236+
from database.agent_db import clear_agent_new_mark
1237+
1238+
rowcount = clear_agent_new_mark(agent_id, tenant_id, user_id)
1239+
logger.info(f"clear_agent_new_mark_impl called for agent_id={agent_id}, tenant_id={tenant_id}, user_id={user_id}, affected_rows={rowcount}")
1240+
return rowcount
1241+
1242+
1243+
async def mark_agents_as_new_impl(agent_ids: list[int], tenant_id: str, user_id: str):
1244+
"""
1245+
Mark a list of agents as new via DB helper
1246+
"""
1247+
# Deprecated: marking is handled at creation time via create_agent().
1248+
# Keep DB helper available in database.agent_db for ad-hoc operations.
1249+
return None
1250+
1251+
12241252
async def list_all_agent_info_impl(tenant_id: str) -> list[dict]:
12251253
"""
12261254
list all agent info
@@ -1275,6 +1303,7 @@ async def list_all_agent_info_impl(tenant_id: str) -> list[dict]:
12751303
"author": agent.get("author"),
12761304
"is_available": len(unavailable_reasons) == 0,
12771305
"unavailable_reasons": unavailable_reasons,
1306+
"is_new": agent.get("is_new", False),
12781307
"group_ids": convert_string_to_list(agent.get("group_ids"))
12791308
})
12801309

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-- Add is_new column to ag_tenant_agent_t table for new agent marking
2+
-- This migration adds a field to track whether an agent is marked as new for users
3+
4+
-- Add is_new column with default value false
5+
ALTER TABLE nexent.ag_tenant_agent_t
6+
ADD COLUMN IF NOT EXISTS is_new BOOLEAN DEFAULT FALSE;
7+
8+
-- Add comment for the new column
9+
COMMENT ON COLUMN nexent.ag_tenant_agent_t.is_new IS 'Whether this agent is marked as new for the user';
10+
11+
-- Create index for performance on is_new queries
12+
CREATE INDEX IF NOT EXISTS idx_ag_tenant_agent_t_is_new
13+
ON nexent.ag_tenant_agent_t (tenant_id, is_new)
14+
WHERE delete_flag = 'N';
15+
16+

frontend/app/[locale]/agents/components/AgentManageComp.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import log from "@/lib/logger";
1717
import { useState, useEffect } from "react";
1818
import { ImportAgentData } from "@/hooks/useAgentImport";
1919
import AgentImportWizard from "@/components/agent/AgentImportWizard";
20+
import { clearAgentNewMark } from "@/services/agentConfigService";
21+
import { clearAgentAndSync } from "@/lib/agentNewUtils";
2022

2123
export default function AgentManageComp() {
2224
const { t } = useTranslation("common");
@@ -156,6 +158,16 @@ export default function AgentManageComp() {
156158
return;
157159
}
158160

161+
// Clear NEW mark when agent is selected for editing
162+
try {
163+
const res = await clearAgentAndSync(agent.id, queryClient);
164+
if (!res?.success) {
165+
log.warn("Failed to clear NEW mark on select:", res);
166+
}
167+
} catch (err) {
168+
log.error("Failed to clear NEW mark on select:", err);
169+
}
170+
159171
// Set selected agent id to trigger the hook
160172
setSelectedAgentId(Number(agent.id));
161173
};

frontend/app/[locale]/agents/components/agentManage/AgentList.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import React, { useState } from "react";
3+
import React, { useState, useEffect } from "react";
44
import { useTranslation } from "react-i18next";
55
import { Button, Col, Flex, Tooltip, Divider, Table, theme, App } from "antd";
66
import { ExclamationCircleOutlined } from "@ant-design/icons";
@@ -17,7 +17,9 @@ import {
1717
exportAgent,
1818
updateToolConfig,
1919
} from "@/services/agentConfigService";
20+
import { clearAgentNewMark } from "@/services/agentConfigService";
2021
import log from "@/lib/logger";
22+
import { clearAgentAndSync } from "@/lib/agentNewUtils";
2123

2224
interface AgentListProps {
2325
agentList: Agent[];
@@ -40,6 +42,27 @@ export default function AgentList({
4042
const confirm = useConfirmModal();
4143
const queryClient = useQueryClient();
4244

45+
// Note: rely on agent.is_new from agentList (single source of truth).
46+
// Clear NEW mark when agent is selected (sync with selection visual feedback)
47+
useEffect(() => {
48+
if (currentAgentId) {
49+
const agentId = String(currentAgentId);
50+
const agent = agentList.find(a => String(a.id) === agentId);
51+
if (agent?.is_new) {
52+
(async () => {
53+
try {
54+
const res = await clearAgentAndSync(agentId, queryClient);
55+
if (!res?.success) {
56+
log.warn("Failed to clear NEW mark for agent:", agentId, res);
57+
}
58+
} catch (err) {
59+
log.error("Error clearing NEW mark:", err);
60+
}
61+
})();
62+
}
63+
}
64+
}, [currentAgentId, agentList]);
65+
4366
// Call relationship modal state
4467
const [callRelationshipModalVisible, setCallRelationshipModalVisible] =
4568
useState(false);
@@ -279,6 +302,8 @@ export default function AgentList({
279302
onClick: (e: any) => {
280303
e.preventDefault();
281304
e.stopPropagation();
305+
306+
// Call onSelectAgent - NEW mark clearing is handled by useEffect
282307
onSelectAgent(agent);
283308
},
284309
})}
@@ -292,6 +317,7 @@ export default function AgentList({
292317
const isSelected =
293318
currentAgentId !== null &&
294319
String(currentAgentId) === String(agent.id);
320+
const isNew = agent.is_new || false;
295321

296322
return (
297323
<Flex
@@ -332,6 +358,13 @@ export default function AgentList({
332358
<ExclamationCircleOutlined className="text-amber-500 text-sm flex-shrink-0 cursor-pointer" />
333359
</Tooltip>
334360
)}
361+
{isNew && (
362+
<Tooltip title={t("space.new", "New imported agent")}>
363+
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400 flex-shrink-0">
364+
NEW
365+
</span>
366+
</Tooltip>
367+
)}
335368
{displayName && (
336369
<span className="text-base leading-normal max-w-[220px] truncate break-all">
337370
{displayName}

frontend/app/[locale]/chat/components/chatAgentSelector.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import { fetchAllAgents } from "@/services/agentConfigService";
99
import { getUrlParam } from "@/lib/utils";
1010
import log from "@/lib/logger";
1111
import { Agent, ChatAgentSelectorProps } from "@/types/chat";
12+
import { clearAgentNewMark } from "@/services/agentConfigService";
13+
import { useQueryClient } from "@tanstack/react-query";
14+
import { clearAgentAndSync } from "@/lib/agentNewUtils";
1215

1316
export function ChatAgentSelector({
1417
selectedAgentId,
@@ -34,6 +37,7 @@ export function ChatAgentSelector({
3437
const selectedAgent = agents.find(
3538
(agent) => agent.agent_id === selectedAgentId
3639
);
40+
const queryClient = useQueryClient();
3741

3842
// Detect duplicate agent names and mark later-added agents as disabled
3943
// For agents with the same name, keep the first one (smallest ID) enabled, disable the rest
@@ -198,18 +202,28 @@ export function ChatAgentSelector({
198202
}
199203
};
200204

201-
const handleAgentSelect = (agentId: number | null) => {
205+
const handleAgentSelect = async (agentId: number | null) => {
202206
// Only effectively available agents can be selected
203207
if (agentId !== null) {
204208
const agent = agents.find((a) => a.agent_id === agentId);
205209
if (agent) {
206210
const isAvailableTool = agent.is_available !== false;
207211
const isDuplicateDisabled = duplicateAgentInfo.disabledAgentIds.has(agent.agent_id);
208212
const isEffectivelyAvailable = isAvailableTool && !isDuplicateDisabled;
209-
213+
210214
if (!isEffectivelyAvailable) {
211215
return; // Unavailable agents cannot be selected
212216
}
217+
218+
// Clear NEW mark when agent is selected for chat
219+
try {
220+
const res = await clearAgentAndSync(agentId, queryClient);
221+
if (!res?.success) {
222+
log.warn("Failed to clear NEW mark on select:", res);
223+
}
224+
} catch (e) {
225+
log.error("Failed to clear NEW mark on select:", e);
226+
}
213227
}
214228
}
215229

0 commit comments

Comments
 (0)