mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat: Add isDefault field to AIAgent model and update service logic for default agent handling
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user