feat: Add isDefault field to AIAgent model and update service logic for default agent handling

This commit is contained in:
Nawaz Dhandala
2025-12-28 10:46:44 +00:00
parent c8df86011c
commit 877a97017d
4 changed files with 221 additions and 0 deletions

View File

@@ -508,6 +508,38 @@ export default class AIAgent extends BaseModel {
})
public connectionStatus?: AIAgentConnectionStatus = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectAIAgent,
],
read: [Permission.Public],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.EditProjectAIAgent,
],
})
@TableColumn({
isDefaultValueColumn: true,
required: true,
type: TableColumnType.Boolean,
title: "Is Default",
description:
"Is this the default AI Agent for the project? When set, this agent will be used for automated tasks.",
defaultValue: false,
})
@Column({
type: ColumnType.Boolean,
nullable: false,
unique: false,
default: false,
})
public isDefault?: boolean = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,

View File

@@ -34,6 +34,7 @@ import { WhatsAppMessagePayload } from "../../Types/WhatsApp/WhatsAppMessage";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import { IsBillingEnabled } from "../EnvironmentConfig";
import GlobalCache from "../Infrastructure/GlobalCache";
import QueryHelper from "../Types/Database/QueryHelper";
export class Service extends DatabaseService<Model> {
public constructor() {
@@ -138,6 +139,29 @@ export class Service extends DatabaseService<Model> {
createBy.data.aiAgentVersion = new Version("1.0.0");
}
// When creating a new AI Agent, set it as default by default
if (createBy.data.isDefault === undefined) {
createBy.data.isDefault = true;
}
// If this agent is being set as default, unset other defaults in the same project
if (createBy.data.isDefault && createBy.data.projectId) {
await this.updateBy({
query: {
projectId: createBy.data.projectId,
isDefault: true,
},
data: {
isDefault: false,
},
props: {
isRoot: true,
},
limit: LIMIT_MAX,
skip: 0,
});
}
return { createBy: createBy, carryForward: [] };
}
@@ -249,6 +273,55 @@ export class Service extends DatabaseService<Model> {
carryForward.aiAgentsToNotifyOwners = aiAgentsToNotifyOwners;
}
// If setting isDefault to true, we need to unset other defaults in the same project
if (updateBy.data.isDefault === true) {
// Get the items being updated to find their project IDs
const itemsToUpdate: Array<Model> = await this.findBy({
query: updateBy.query,
select: {
_id: true,
projectId: true,
},
props: {
isRoot: true,
},
limit: LIMIT_MAX,
skip: 0,
});
// Collect unique project IDs
const projectIds: Set<string> = new Set();
const itemIds: Set<string> = new Set();
for (const item of itemsToUpdate) {
if (item.projectId) {
projectIds.add(item.projectId.toString());
}
if (item._id) {
itemIds.add(item._id);
}
}
// For each project, unset the default on other agents
for (const projectIdStr of projectIds) {
const projectId: ObjectID = new ObjectID(projectIdStr);
await this.updateBy({
query: {
projectId: projectId,
isDefault: true,
_id: QueryHelper.notInOrNull(Array.from(itemIds)),
},
data: {
isDefault: false,
},
props: {
isRoot: true,
},
limit: LIMIT_MAX,
skip: 0,
});
}
}
return { updateBy: updateBy, carryForward };
}
@@ -429,6 +502,63 @@ export class Service extends DatabaseService<Model> {
`/${projectId.toString()}/settings/ai-agents/${aiAgentId.toString()}`,
);
}
@CaptureSpan()
public async getAIAgentForProject(
projectId: ObjectID,
): Promise<Model | null> {
// First try to get the default AI agent for the project
let agent: Model | null = await this.findOneBy({
query: {
projectId: projectId,
isDefault: true,
},
select: {
_id: true,
name: true,
description: true,
key: true,
aiAgentVersion: true,
connectionStatus: true,
isGlobalAIAgent: true,
lastAlive: true,
},
props: {
isRoot: true,
},
});
if (agent) {
return agent;
}
// If no default agent, get any global AI agent
agent = await this.findOneBy({
query: {
projectId: QueryHelper.isNull(),
isGlobalAIAgent: true,
},
select: {
_id: true,
name: true,
description: true,
key: true,
aiAgentVersion: true,
connectionStatus: true,
isGlobalAIAgent: true,
lastAlive: true,
},
props: {
isRoot: true,
},
});
if (agent) {
return agent;
}
return null;
}
}
export default new Service();

View File

@@ -30,6 +30,8 @@ import Team from "Common/Models/DatabaseModels/Team";
import ResetObjectID from "Common/UI/Components/ResetObjectID/ResetObjectID";
import AIAgentStatusElement from "../../Components/AIAgent/AIAgentStatus";
import CustomAIAgentDocumentation from "../../Components/AIAgent/CustomAIAgentDocumentation";
import Pill from "Common/UI/Components/Pill/Pill";
import { Green } from "Common/Types/BrandColors";
export enum PermissionType {
AllowPermissions = "AllowPermissions",
@@ -99,6 +101,17 @@ const AIAgentView: FunctionComponent<PageComponentProps> = (
required: false,
placeholder: "Upload logo",
},
{
field: {
isDefault: true,
},
title: "Set as Default",
stepId: "more",
fieldType: FormFieldSchemaType.Toggle,
required: false,
description:
"Set this as the default AI Agent for the project. When a default is set, this agent will be used for automated tasks.",
},
{
field: {
labels: true,
@@ -153,6 +166,19 @@ const AIAgentView: FunctionComponent<PageComponentProps> = (
title: "AI Agent Key",
fieldType: FieldType.HiddenText,
},
{
field: {
isDefault: true,
},
title: "Default",
fieldType: FieldType.Boolean,
getElement: (item: AIAgent): ReactElement => {
if (item.isDefault) {
return <Pill text="Default" color={Green} />;
}
return <span className="text-gray-400">-</span>;
},
},
{
field: {
labels: {

View File

@@ -22,6 +22,8 @@ import React, {
useState,
} from "react";
import LabelsElement from "Common/UI/Components/Label/Labels";
import Pill from "Common/UI/Components/Pill/Pill";
import { Green } from "Common/Types/BrandColors";
const AIAgentsPage: FunctionComponent<
PageComponentProps
@@ -180,6 +182,17 @@ const AIAgentsPage: FunctionComponent<
required: false,
placeholder: "Upload logo",
},
{
field: {
isDefault: true,
},
title: "Set as Default",
stepId: "more",
fieldType: FormFieldSchemaType.Toggle,
required: false,
description:
"Set this as the default AI Agent for the project. When a default is set, this agent will be used for automated tasks.",
},
{
field: {
labels: true,
@@ -236,6 +249,13 @@ const AIAgentsPage: FunctionComponent<
title: "Description",
type: FieldType.Text,
},
{
field: {
isDefault: true,
},
title: "Default",
type: FieldType.Boolean,
},
{
title: "Labels",
type: FieldType.EntityArray,
@@ -285,6 +305,19 @@ const AIAgentsPage: FunctionComponent<
return <AIAgentStatusElement aiAgent={item} />;
},
},
{
field: {
isDefault: true,
},
title: "Default",
type: FieldType.Boolean,
getElement: (item: AIAgent): ReactElement => {
if (item.isDefault) {
return <Pill text="Default" color={Green} />;
}
return <span className="text-gray-400">-</span>;
},
},
{
field: {
labels: {