feat: Add category and support contact fields to Agent schema and UI components

This commit is contained in:
“Praneeth 2025-05-22 11:57:03 +02:00
parent e86842fd19
commit c43a52b4c9
15 changed files with 581 additions and 11 deletions

View file

@ -12,6 +12,61 @@ const {
} = require('./Project'); } = require('./Project');
const getLogStores = require('~/cache/getLogStores'); const getLogStores = require('~/cache/getLogStores');
// Category values - must match the frontend values in client/src/constants/agentCategories.ts
const CATEGORY_VALUES = {
GENERAL: 'general',
HR: 'hr',
RD: 'rd',
FINANCE: 'finance',
IT: 'it',
SALES: 'sales',
AFTERSALES: 'aftersales',
};
const VALID_CATEGORIES = Object.values(CATEGORY_VALUES);
// Add category field to the Agent schema if it doesn't already exist
if (!agentSchema.paths.category) {
agentSchema.add({
category: {
type: String,
trim: true,
enum: {
values: VALID_CATEGORIES,
message:
'"{VALUE}" is not a supported agent category. Valid categories are: ' +
VALID_CATEGORIES.join(', ') +
'.',
},
index: true,
default: CATEGORY_VALUES.GENERAL,
},
});
}
// Add support_contact field to the Agent schema if it doesn't already exist
if (!agentSchema.paths.support_contact) {
agentSchema.add({
support_contact: {
type: Object,
default: {},
name: {
type: String,
minlength: [3, 'Support contact name must be at least 3 characters.'],
trim: true,
},
email: {
type: String,
match: [
/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/,
'Please enter a valid email address.',
],
trim: true,
},
},
});
}
const Agent = mongoose.model('agent', agentSchema); const Agent = mongoose.model('agent', agentSchema);
/** /**
@ -21,7 +76,12 @@ const Agent = mongoose.model('agent', agentSchema);
* @throws {Error} If the agent creation fails. * @throws {Error} If the agent creation fails.
*/ */
const createAgent = async (agentData) => { const createAgent = async (agentData) => {
return (await Agent.create(agentData)).toObject(); // Ensure the agent has a category (default to 'general' if none provided)
const dataWithCategory = {
...agentData,
category: agentData.category || 'general',
};
return (await Agent.create(dataWithCategory)).toObject();
}; };
/** /**
@ -280,6 +340,7 @@ const getListAgents = async (searchParameter) => {
projectIds: 1, projectIds: 1,
description: 1, description: 1,
isCollaborative: 1, isCollaborative: 1,
category: 1,
}).lean() }).lean()
).map((agent) => { ).map((agent) => {
if (agent.author?.toString() !== author) { if (agent.author?.toString() !== author) {

View file

@ -16,6 +16,11 @@ export type TAgentCapabilities = {
[AgentCapabilities.hide_sequential_outputs]?: boolean; [AgentCapabilities.hide_sequential_outputs]?: boolean;
}; };
export type SupportContact = {
name?: string;
email?: string;
};
export type AgentForm = { export type AgentForm = {
agent?: TAgentOption; agent?: TAgentOption;
id: string; id: string;
@ -29,4 +34,5 @@ export type AgentForm = {
agent_ids?: string[]; agent_ids?: string[];
[AgentCapabilities.artifacts]?: ArtifactModes | string; [AgentCapabilities.artifacts]?: ArtifactModes | string;
recursion_limit?: number; recursion_limit?: number;
support_contact?: SupportContact;
} & TAgentCapabilities; } & TAgentCapabilities;

View file

@ -0,0 +1,62 @@
import React from 'react';
import { useAgentCategories } from '~/hooks/Agents';
import { cn } from '~/utils';
interface AgentCategoryDisplayProps {
category?: string;
className?: string;
showIcon?: boolean;
iconClassName?: string;
showEmptyFallback?: boolean;
}
/**
* Component to display an agent category with proper translation
*
* @param category - The category value (e.g., "general", "hr", etc.)
* @param className - Optional className for the container
* @param showIcon - Whether to show the category icon
* @param iconClassName - Optional className for the icon
* @param showEmptyFallback - Whether to show a fallback for empty categories
*/
const AgentCategoryDisplay: React.FC<AgentCategoryDisplayProps> = ({
category,
className = '',
showIcon = true,
iconClassName = 'h-4 w-4 mr-2',
showEmptyFallback = false,
}) => {
const { categories, emptyCategory } = useAgentCategories();
// Find the category in our processed categories list
const categoryItem = categories.find((c) => c.value === category);
// Handle empty string case differently than undefined/null
if (category === '') {
if (!showEmptyFallback) {
return null;
}
// Show the empty category placeholder
return (
<div className={cn('flex items-center text-gray-400', className)}>
<span>{emptyCategory.label}</span>
</div>
);
}
// No category or unknown category
if (!category || !categoryItem) {
return null;
}
return (
<div className={cn('flex items-center', className)}>
{showIcon && categoryItem.icon && (
<span className={cn('flex-shrink-0', iconClassName)}>{categoryItem.icon}</span>
)}
<span>{categoryItem.label}</span>
</div>
);
};
export default AgentCategoryDisplay;

View file

@ -0,0 +1,94 @@
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
useFormContext,
Controller,
useWatch,
ControllerRenderProps,
FieldValues,
FieldPath,
} from 'react-hook-form';
import ControlCombobox from '~/components/ui/ControlCombobox';
import { useAgentCategories } from '~/hooks/Agents';
import { OptionWithIcon } from '~/common/types';
import { cn } from '~/utils';
/**
* A component for selecting agent categories with form validation
*/
const AgentCategorySelector: React.FC = () => {
const { t } = useTranslation();
const formContext = useFormContext();
const { categories } = useAgentCategories();
// Methods
const handleCategorySync = (
field: ControllerRenderProps<FieldValues, FieldPath<FieldValues>>,
agent_id: string | null,
) => {
useEffect(() => {
if (agent_id === '') {
// Form has been reset, ensure field is set to default
if (field.value !== 'general') {
field.onChange('general');
}
}
// If value is empty or undefined, default to 'general'
if (!field.value) {
field.onChange('general');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [field]); // Removing agent_id from dependencies as suggested by ESLint
};
const getCategoryDisplayValue = (value: string) => {
const categoryItem = comboboxItems.find((c) => c.value === value);
return categoryItem?.label || comboboxItems.find((c) => c.value === 'general')?.label;
};
// Always call useWatch
const agent_id = useWatch({
name: 'id',
control: formContext.control,
});
// Transform categories to the format expected by ControlCombobox
const comboboxItems = categories.map((category) => ({
label: category.label,
value: category.value,
}));
const searchPlaceholder = t('com_ui_search_agent_category', 'Search categories...');
const ariaLabel = t('com_ui_agent_category_selector_aria', "Agent's category selector");
return (
<Controller
name="category"
control={formContext.control}
defaultValue="general"
render={({ field }) => {
handleCategorySync(field, agent_id);
const displayValue = getCategoryDisplayValue(field.value);
return (
<ControlCombobox
selectedValue={field.value || 'general'}
displayValue={displayValue}
searchPlaceholder={searchPlaceholder}
setValue={(value) => {
field.onChange(value || 'general');
}}
items={comboboxItems}
className=""
ariaLabel={ariaLabel}
isCollapsed={false}
showCarat={true}
/>
);
}}
/>
);
};
export default AgentCategorySelector;

View file

@ -18,6 +18,7 @@ import FileSearch from './FileSearch';
import Artifacts from './Artifacts'; import Artifacts from './Artifacts';
import AgentTool from './AgentTool'; import AgentTool from './AgentTool';
import CodeForm from './Code/Form'; import CodeForm from './Code/Form';
import AgentCategorySelector from './AgentCategorySelector';
import { Panel } from '~/common'; import { Panel } from '~/common';
const labelClass = 'mb-2 text-token-text-primary block font-medium'; const labelClass = 'mb-2 text-token-text-primary block font-medium';
@ -228,6 +229,14 @@ export default function AgentConfig({
)} )}
/> />
</div> </div>
{/* Category */}
<div className="mb-4">
<label className={labelClass} htmlFor="category-selector">
{localize('com_ui_category')} <span className="text-red-500">*</span>
</label>
<AgentCategorySelector className="w-full" />
</div>
{/* Instructions */} {/* Instructions */}
<Instructions /> <Instructions />
{/* Model and Provider */} {/* Model and Provider */}
@ -329,6 +338,88 @@ export default function AgentConfig({
</div> </div>
</div> </div>
</div> </div>
{/* Support Contact (Optional) */}
<div className="mb-4">
<div className="mb-1.5 flex items-center gap-2">
<span>
<label className="text-token-text-primary block font-medium">
{localize('com_ui_support_contact')}
</label>
</span>
</div>
<div className="space-y-3">
{/* Support Contact Name */}
<div className="flex flex-col">
<label className="flex items-center justify-between mb-1" htmlFor="support-contact-name">
<span className="text-sm">{localize('com_ui_support_contact_name')}</span>
</label>
<Controller
name="support_contact.name"
control={control}
rules={{
minLength: {
value: 3,
message: localize('com_ui_support_contact_name_min_length', { minLength: 3 }),
},
}}
render={({ field, fieldState: { error } }) => (
<>
<input
{...field}
value={field.value ?? ''}
className={cn(inputClass, error ? 'border-2 border-red-500' : '')}
id="support-contact-name"
type="text"
placeholder={localize('com_ui_support_contact_name_placeholder')}
aria-label="Support contact name"
/>
{error && (
<span className="text-sm text-red-500 transition duration-300 ease-in-out">
{error.message}
</span>
)}
</>
)}
/>
</div>
{/* Support Contact Email */}
<div className="flex flex-col">
<label className="flex items-center justify-between mb-1" htmlFor="support-contact-email">
<span className="text-sm">{localize('com_ui_support_contact_email')}</span>
</label>
<Controller
name="support_contact.email"
control={control}
rules={{
pattern: {
value: /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/,
message: localize('com_ui_support_contact_email_invalid'),
},
}}
render={({ field, fieldState: { error } }) => (
<>
<input
{...field}
value={field.value ?? ''}
className={cn(inputClass, error ? 'border-2 border-red-500' : '')}
id="support-contact-email"
type="email"
placeholder={localize('com_ui_support_contact_email_placeholder')}
aria-label="Support contact email"
/>
{error && (
<span className="text-sm text-red-500 transition duration-300 ease-in-out">
{error.message}
</span>
)}
</>
)}
/>
</div>
</div>
</div>
</div> </div>
<ToolSelectDialog <ToolSelectDialog
isOpen={showToolDialog} isOpen={showToolDialog}

View file

@ -140,6 +140,8 @@ export default function AgentPanel({
end_after_tools, end_after_tools,
hide_sequential_outputs, hide_sequential_outputs,
recursion_limit, recursion_limit,
category,
support_contact,
} = data; } = data;
const model = _model ?? ''; const model = _model ?? '';
@ -162,6 +164,8 @@ export default function AgentPanel({
end_after_tools, end_after_tools,
hide_sequential_outputs, hide_sequential_outputs,
recursion_limit, recursion_limit,
category,
support_contact,
}, },
}); });
return; return;
@ -187,6 +191,8 @@ export default function AgentPanel({
end_after_tools, end_after_tools,
hide_sequential_outputs, hide_sequential_outputs,
recursion_limit, recursion_limit,
category,
support_contact,
}); });
}, },
[agent_id, create, update, showToast, localize], [agent_id, create, update, showToast, localize],
@ -220,7 +226,7 @@ export default function AgentPanel({
className="scrollbar-gutter-stable h-auto w-full flex-shrink-0 overflow-x-hidden" className="scrollbar-gutter-stable h-auto w-full flex-shrink-0 overflow-x-hidden"
aria-label="Agent configuration form" aria-label="Agent configuration form"
> >
<div className="mt-2 flex w-full flex-wrap gap-2"> <div className="mx-1 mt-2 flex w-full flex-wrap gap-2">
<div className="w-full"> <div className="w-full">
<AgentSelect <AgentSelect
createMutation={create} createMutation={create}

View file

@ -73,6 +73,10 @@ export default function AgentSelect({
agent: update, agent: update,
model: update.model, model: update.model,
tools: agentTools, tools: agentTools,
// Ensure the category is properly set for the form
category: fullAgent.category || 'general',
// Make sure support_contact is properly loaded
support_contact: fullAgent.support_contact,
}; };
Object.entries(fullAgent).forEach(([name, value]) => { Object.entries(fullAgent).forEach(([name, value]) => {

View file

@ -0,0 +1,90 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import AgentCategoryDisplay from '../AgentCategoryDisplay';
// Mock the useAgentCategories hook
jest.mock('~/hooks/Agents', () => ({
useAgentCategories: () => ({
categories: [
{
value: 'general',
label: 'General',
icon: <span data-testid="icon-general">Icon</span>,
className: 'w-full'
},
{
value: 'hr',
label: 'HR',
icon: <span data-testid="icon-hr">Icon</span>,
className: 'w-full'
},
{
value: 'rd',
label: 'R&D',
icon: <span data-testid="icon-rd">Icon</span>,
className: 'w-full'
},
{
value: 'finance',
label: 'Finance',
icon: <span data-testid="icon-finance">Icon</span>,
className: 'w-full'
},
],
emptyCategory: {
value: '',
label: 'General',
className: 'w-full'
}
})
}));
describe('AgentCategoryDisplay', () => {
it('should display the proper label for a category', () => {
render(<AgentCategoryDisplay category="rd" />);
expect(screen.getByText('R&D')).toBeInTheDocument();
});
it('should display the icon when showIcon is true', () => {
render(<AgentCategoryDisplay category="finance" showIcon={true} />);
expect(screen.getByTestId('icon-finance')).toBeInTheDocument();
expect(screen.getByText('Finance')).toBeInTheDocument();
});
it('should not display the icon when showIcon is false', () => {
render(<AgentCategoryDisplay category="hr" showIcon={false} />);
expect(screen.queryByTestId('icon-hr')).not.toBeInTheDocument();
expect(screen.getByText('HR')).toBeInTheDocument();
});
it('should apply custom classnames', () => {
render(<AgentCategoryDisplay category="general" className="test-class" />);
expect(screen.getByText('General').parentElement).toHaveClass('test-class');
});
it('should not render anything for unknown categories', () => {
const { container } = render(<AgentCategoryDisplay category="unknown" />);
expect(container).toBeEmptyDOMElement();
});
it('should not render anything when no category is provided', () => {
const { container } = render(<AgentCategoryDisplay />);
expect(container).toBeEmptyDOMElement();
});
it('should not render anything for empty category when showEmptyFallback is false', () => {
const { container } = render(<AgentCategoryDisplay category="" />);
expect(container).toBeEmptyDOMElement();
});
it('should render empty category placeholder when showEmptyFallback is true', () => {
render(<AgentCategoryDisplay category="" showEmptyFallback={true} />);
expect(screen.getByText('General')).toBeInTheDocument();
});
it('should apply custom iconClassName to the icon', () => {
render(<AgentCategoryDisplay category="general" iconClassName="custom-icon-class" />);
const iconElement = screen.getByTestId('icon-general').parentElement;
expect(iconElement).toHaveClass('custom-icon-class');
});
});

View file

@ -0,0 +1,48 @@
import { TranslationKeys } from '~/hooks/useLocalize';
export interface AgentCategory {
label: TranslationKeys;
value: string;
}
// Category values - must match the backend values in Agent.js
export const CATEGORY_VALUES = {
GENERAL: 'general',
HR: 'hr',
RD: 'rd',
FINANCE: 'finance',
IT: 'it',
SALES: 'sales',
AFTERSALES: 'aftersales',
} as const;
// Type for category values to ensure type safety
export type AgentCategoryValue = (typeof CATEGORY_VALUES)[keyof typeof CATEGORY_VALUES];
// Display labels for each category
const CATEGORY_LABELS = {
[CATEGORY_VALUES.GENERAL]: 'com_ui_agent_category_general',
[CATEGORY_VALUES.HR]: 'com_ui_agent_category_hr',
[CATEGORY_VALUES.RD]: 'com_ui_agent_category_rd', // R&D
[CATEGORY_VALUES.FINANCE]: 'com_ui_agent_category_finance',
[CATEGORY_VALUES.IT]: 'com_ui_agent_category_it',
[CATEGORY_VALUES.SALES]: 'com_ui_agent_category_sales',
[CATEGORY_VALUES.AFTERSALES]: 'com_ui_agent_category_aftersales',
} as const;
// The categories array used in the UI
export const AGENT_CATEGORIES: AgentCategory[] = [
{ value: CATEGORY_VALUES.GENERAL, label: CATEGORY_LABELS[CATEGORY_VALUES.GENERAL] },
{ value: CATEGORY_VALUES.HR, label: CATEGORY_LABELS[CATEGORY_VALUES.HR] },
{ value: CATEGORY_VALUES.RD, label: CATEGORY_LABELS[CATEGORY_VALUES.RD] },
{ value: CATEGORY_VALUES.FINANCE, label: CATEGORY_LABELS[CATEGORY_VALUES.FINANCE] },
{ value: CATEGORY_VALUES.IT, label: CATEGORY_LABELS[CATEGORY_VALUES.IT] },
{ value: CATEGORY_VALUES.SALES, label: CATEGORY_LABELS[CATEGORY_VALUES.SALES] },
{ value: CATEGORY_VALUES.AFTERSALES, label: CATEGORY_LABELS[CATEGORY_VALUES.AFTERSALES] },
];
// The empty category placeholder
export const EMPTY_AGENT_CATEGORY: AgentCategory = {
value: '',
label: 'com_ui_agent_category_general',
};

View file

@ -0,0 +1,37 @@
import { renderHook } from '@testing-library/react-hooks';
import useAgentCategories from '../useAgentCategories';
import { AGENT_CATEGORIES, EMPTY_AGENT_CATEGORY } from '~/constants/agentCategories';
// Mock the useLocalize hook
jest.mock('~/hooks/useLocalize', () => ({
__esModule: true,
default: () => (key: string) => {
// Simple mock implementation that returns the key as the translation
return key === 'com_ui_agent_category_general'
? 'General (Translated)'
: key;
},
}));
describe('useAgentCategories', () => {
it('should return processed categories with correct structure', () => {
const { result } = renderHook(() => useAgentCategories());
// Check that we have the expected number of categories
expect(result.current.categories.length).toBe(AGENT_CATEGORIES.length);
// Check that the first category has the expected structure
const firstCategory = result.current.categories[0];
const firstOriginalCategory = AGENT_CATEGORIES[0];
expect(firstCategory.value).toBe(firstOriginalCategory.value);
// Check that labels are properly translated
expect(firstCategory.label).toBe('General (Translated)');
expect(firstCategory.className).toBe('w-full');
// Check the empty category
expect(result.current.emptyCategory.value).toBe(EMPTY_AGENT_CATEGORY.value);
expect(result.current.emptyCategory.label).toBeTruthy();
});
});

View file

@ -1,2 +1,4 @@
export { default as useAgentsMap } from './useAgentsMap'; export { default as useAgentsMap } from './useAgentsMap';
export { default as useSelectAgent } from './useSelectAgent'; export { default as useSelectAgent } from './useSelectAgent';
export { default as useAgentCategories } from './useAgentCategories';
export type { ProcessedAgentCategory } from './useAgentCategories';

View file

@ -0,0 +1,44 @@
import { useMemo } from 'react';
import useLocalize from '~/hooks/useLocalize';
import { AGENT_CATEGORIES, EMPTY_AGENT_CATEGORY } from '~/constants/agentCategories';
import { ReactNode } from 'react';
// This interface matches the structure used by the ControlCombobox component
export interface ProcessedAgentCategory {
label: string; // Translated label
value: string; // Category value
className?: string;
icon?: ReactNode; // Optional icon for the category
}
/**
* Custom hook that provides processed and translated agent categories
*
* @returns Object containing categories and emptyCategory
*/
const useAgentCategories = () => {
const localize = useLocalize();
const categories = useMemo((): ProcessedAgentCategory[] => {
return AGENT_CATEGORIES.map((category) => ({
label: localize(category.label),
value: category.value,
className: 'w-full',
// Note: Icons for categories should be handled separately
// This fixes the interface but doesn't implement icons
}));
}, [localize]);
const emptyCategory = useMemo(
(): ProcessedAgentCategory => ({
label: localize(EMPTY_AGENT_CATEGORY.label),
value: EMPTY_AGENT_CATEGORY.value,
className: 'w-full',
}),
[localize],
);
return { categories, emptyCategory };
};
export default useAgentCategories;

View file

@ -29,7 +29,7 @@
"com_assistants_actions_info": "Let your Assistant retrieve information or take actions via API's", "com_assistants_actions_info": "Let your Assistant retrieve information or take actions via API's",
"com_assistants_add_actions": "Add Actions", "com_assistants_add_actions": "Add Actions",
"com_assistants_add_tools": "Add Tools", "com_assistants_add_tools": "Add Tools",
"com_assistants_allow_sites_you_trust": "Only allow sites you trust", "com_assistants_allow_sites_you_trust": "Only allow sites you trust.",
"com_assistants_append_date": "Append Current Date & Time", "com_assistants_append_date": "Append Current Date & Time",
"com_assistants_append_date_tooltip": "When enabled, the current client date and time will be appended to the assistant system instructions.", "com_assistants_append_date_tooltip": "When enabled, the current client date and time will be appended to the assistant system instructions.",
"com_assistants_attempt_info": "Assistant wants to send the following:", "com_assistants_attempt_info": "Assistant wants to send the following:",
@ -523,6 +523,9 @@
"com_ui_backup_codes_regenerated": "Backup codes have been regenerated successfully", "com_ui_backup_codes_regenerated": "Backup codes have been regenerated successfully",
"com_ui_basic": "Basic", "com_ui_basic": "Basic",
"com_ui_basic_auth_header": "Basic authorization header", "com_ui_basic_auth_header": "Basic authorization header",
"com_ui_client_credential_flow": "Client Credential Flow",
"com_ui_auth_code_flow": "Authorization Code Flow",
"com_ui_oauth_flow": "OAuth Flow",
"com_ui_bearer": "Bearer", "com_ui_bearer": "Bearer",
"com_ui_bookmark_delete_confirm": "Are you sure you want to delete this bookmark?", "com_ui_bookmark_delete_confirm": "Are you sure you want to delete this bookmark?",
"com_ui_bookmarks": "Bookmarks", "com_ui_bookmarks": "Bookmarks",
@ -546,6 +549,15 @@
"com_ui_callback_url": "Callback URL", "com_ui_callback_url": "Callback URL",
"com_ui_cancel": "Cancel", "com_ui_cancel": "Cancel",
"com_ui_category": "Category", "com_ui_category": "Category",
"com_ui_category_required_agent": "Category is required",
"com_ui_agent_category_selector_aria": "Agent's category selector",
"com_ui_agent_category_general": "General",
"com_ui_agent_category_hr": "HR",
"com_ui_agent_category_rd": "R&D",
"com_ui_agent_category_finance": "Finance",
"com_ui_agent_category_it": "IT",
"com_ui_agent_category_sales": "Sales",
"com_ui_agent_category_aftersales": "After Sales",
"com_ui_chat": "Chat", "com_ui_chat": "Chat",
"com_ui_chat_history": "Chat History", "com_ui_chat_history": "Chat History",
"com_ui_clear": "Clear", "com_ui_clear": "Clear",
@ -784,6 +796,7 @@
"com_ui_schema": "Schema", "com_ui_schema": "Schema",
"com_ui_scope": "Scope", "com_ui_scope": "Scope",
"com_ui_search": "Search", "com_ui_search": "Search",
"com_ui_search_agent_category": "Search categories...",
"com_ui_secret_key": "Secret Key", "com_ui_secret_key": "Secret Key",
"com_ui_select": "Select", "com_ui_select": "Select",
"com_ui_select_file": "Select a file", "com_ui_select_file": "Select a file",
@ -826,6 +839,13 @@
"com_ui_stop": "Stop", "com_ui_stop": "Stop",
"com_ui_storage": "Storage", "com_ui_storage": "Storage",
"com_ui_submit": "Submit", "com_ui_submit": "Submit",
"com_ui_support_contact": "Support Contact",
"com_ui_support_contact_name": "Name",
"com_ui_support_contact_name_placeholder": "Support contact name",
"com_ui_support_contact_name_min_length": "Name must be at least {{minLength}} characters",
"com_ui_support_contact_email": "Email",
"com_ui_support_contact_email_placeholder": "support@example.com",
"com_ui_support_contact_email_invalid": "Please enter a valid email address",
"com_ui_teach_or_explain": "Learning", "com_ui_teach_or_explain": "Learning",
"com_ui_temporary": "Temporary Chat", "com_ui_temporary": "Temporary Chat",
"com_ui_terms_and_conditions": "Terms and Conditions", "com_ui_terms_and_conditions": "Terms and Conditions",
@ -871,13 +891,6 @@
"com_ui_x_selected": "{{0}} selected", "com_ui_x_selected": "{{0}} selected",
"com_ui_yes": "Yes", "com_ui_yes": "Yes",
"com_ui_zoom": "Zoom", "com_ui_zoom": "Zoom",
"com_ui_getting_started": "Getting Started",
"com_ui_creating_image": "Creating image. May take a moment",
"com_ui_adding_details": "Adding details",
"com_ui_final_touch": "Final touch",
"com_ui_image_created": "Image created",
"com_ui_edit_editing_image": "Editing image",
"com_ui_image_edited": "Image edited",
"com_user_message": "You", "com_user_message": "You",
"com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint." "com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint."
} }

View file

@ -177,6 +177,11 @@ export const defaultAgentFormValues = {
recursion_limit: undefined, recursion_limit: undefined,
[Tools.execute_code]: false, [Tools.execute_code]: false,
[Tools.file_search]: false, [Tools.file_search]: false,
category: 'general',
support_contact: {
name: '',
email: '',
},
}; };
export const ImageVisionTool: FunctionTool = { export const ImageVisionTool: FunctionTool = {

View file

@ -1,4 +1,9 @@
import { Schema, Document, Types } from 'mongoose'; import { Schema, Document, Types } from 'mongoose';
export interface ISupportContact {
name?: string;
email?: string;
}
export interface IAgent extends Omit<Document, 'model'> { export interface IAgent extends Omit<Document, 'model'> {
id: string; id: string;
name?: string; name?: string;
@ -26,6 +31,8 @@ export interface IAgent extends Omit<Document, 'model'> {
conversation_starters?: string[]; conversation_starters?: string[];
tool_resources?: unknown; tool_resources?: unknown;
projectIds?: Types.ObjectId[]; projectIds?: Types.ObjectId[];
category: string;
support_contact?: ISupportContact;
} }
const agentSchema = new Schema<IAgent>( const agentSchema = new Schema<IAgent>(