mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-19 00:48:08 +01:00
🔧 fix: Merge and Rebase Conflicts
- Move AgentCategory from api/models to @packages/data-schemas structure - Add schema, types, methods, and model following codebase conventions - Implement auto-seeding of default categories during AppService startup - Update marketplace controller to use new data-schemas methods - Remove old model file and standalone seed script refactor: unify agent marketplace to single endpoint with cursor pagination - Replace multiple marketplace routes with unified /marketplace endpoint - Add query string controls: category, search, limit, cursor, promoted, requiredPermission - Implement cursor-based pagination replacing page-based system - Integrate ACL permissions for proper access control - Fix ObjectId constructor error in Agent model - Update React components to use unified useGetMarketplaceAgentsQuery hook - Enhance type safety and remove deprecated useDynamicAgentQuery - Update tests for new marketplace architecture -Known issues: see more button after category switching + Unit tests feat: add icon property to ProcessedAgentCategory interface - Add useMarketplaceAgentsInfiniteQuery and useGetAgentCategoriesQuery to client/src/data-provider/Agents/ - Replace manual pagination in AgentGrid with infinite query pattern - Update imports to use local data provider instead of librechat-data-provider - Add proper permission handling with PERMISSION_BITS.VIEW/EDIT constants - Improve agent access control by adding requiredPermission validation in backend - Remove manual cursor/state management in favor of infinite query built-ins - Maintain existing search and category filtering functionality refactor: consolidate agent marketplace endpoints into main agents API and improve data management consistency - Remove dedicated marketplace controller and routes, merging functionality into main agents v1 API - Add countPromotedAgents function to Agent model for promoted agents count - Enhance getListAgents handler with marketplace filtering (category, search, promoted status) - Move getAgentCategories from marketplace to v1 controller with same functionality - Update agent mutations to invalidate marketplace queries and handle multiple permission levels - Improve cache management by updating all agent query variants (VIEW/EDIT permissions) - Consolidate agent data access patterns for better maintainability and consistency - Remove duplicate marketplace route definitions and middleware selected view only agents injected in the drop down fix: remove minlength validation for support contact name in agent schema feat: add validation and error messages for agent name in AgentConfig and AgentPanel fix: update agent permission check logic in AgentPanel to simplify condition Fix linting WIP Fix Unit tests WIP ESLint fixes eslint fix refactor: enhance isDuplicateVersion function in Agent model for improved comparison logic - Introduced handling for undefined/null values in array and object comparisons. - Normalized array comparisons to treat undefined/null as empty arrays. - Added deep comparison for objects and improved handling of primitive values. - Enhanced projectIds comparison to ensure consistent MongoDB ObjectId handling. refactor: remove redundant properties from IAgent interface in agent schema chore: update localization for agent detail component and clean up imports ci: update access middleware tests chore: remove unused PermissionTypes import from Role model ci: update AclEntry model tests ci: update button accessibility labels in AgentDetail tests refactor: update exhaustive dep. lint warning
This commit is contained in:
parent
f59ef0ecb0
commit
526b1980bc
60 changed files with 1524 additions and 2004 deletions
|
|
@ -451,69 +451,23 @@ export const revertAgentVersion = ({
|
|||
* Get agent categories with counts for marketplace tabs
|
||||
*/
|
||||
export const getAgentCategories = (): Promise<t.TMarketplaceCategory[]> => {
|
||||
return request.get(endpoints.agents({ path: 'marketplace/categories' }));
|
||||
return request.get(endpoints.agents({ path: 'categories' }));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get promoted/top picks agents with pagination
|
||||
* Unified marketplace agents endpoint with query string controls
|
||||
*/
|
||||
export const getPromotedAgents = (params: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
showAll?: string; // Add showAll parameter to get all shared agents instead of just promoted
|
||||
}): Promise<a.AgentListResponse> => {
|
||||
return request.get(
|
||||
endpoints.agents({
|
||||
path: 'marketplace/promoted',
|
||||
options: params,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all agents with pagination (for "all" category)
|
||||
*/
|
||||
export const getAllAgents = (params: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<a.AgentListResponse> => {
|
||||
return request.get(
|
||||
endpoints.agents({
|
||||
path: 'marketplace/all',
|
||||
options: params,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get agents by category with pagination
|
||||
*/
|
||||
export const getAgentsByCategory = (params: {
|
||||
category: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<a.AgentListResponse> => {
|
||||
const { category, ...options } = params;
|
||||
return request.get(
|
||||
endpoints.agents({
|
||||
path: `marketplace/category/${category}`,
|
||||
options,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Search agents in marketplace
|
||||
*/
|
||||
export const searchAgents = (params: {
|
||||
q: string;
|
||||
export const getMarketplaceAgents = (params: {
|
||||
requiredPermission: number;
|
||||
category?: string;
|
||||
page?: number;
|
||||
search?: string;
|
||||
limit?: number;
|
||||
cursor?: string;
|
||||
promoted?: 0 | 1;
|
||||
}): Promise<a.AgentListResponse> => {
|
||||
return request.get(
|
||||
endpoints.agents({
|
||||
path: 'marketplace/search',
|
||||
// path: 'marketplace',
|
||||
options: params,
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ import * as dataService from './data-service';
|
|||
export * from './utils';
|
||||
export * from './actions';
|
||||
export { default as createPayload } from './createPayload';
|
||||
// /* react query hooks */
|
||||
// export * from './react-query/react-query-service';
|
||||
/* feedback */
|
||||
export * from './feedback';
|
||||
export * from './parameterSettings';
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ export enum QueryKeys {
|
|||
promptGroup = 'promptGroup',
|
||||
categories = 'categories',
|
||||
randomPrompts = 'randomPrompts',
|
||||
agentCategories = 'agentCategories',
|
||||
marketplaceAgents = 'marketplaceAgents',
|
||||
roles = 'roles',
|
||||
conversationTags = 'conversationTags',
|
||||
health = 'health',
|
||||
|
|
|
|||
|
|
@ -196,6 +196,10 @@ export interface AgentFileResource extends AgentBaseResource {
|
|||
*/
|
||||
vector_store_ids?: Array<string>;
|
||||
}
|
||||
export type SupportContact = {
|
||||
name?: string;
|
||||
email?: string;
|
||||
};
|
||||
|
||||
export type Agent = {
|
||||
_id?: string;
|
||||
|
|
@ -228,6 +232,8 @@ export type Agent = {
|
|||
recursion_limit?: number;
|
||||
isPublic?: boolean;
|
||||
version?: number;
|
||||
category?: string;
|
||||
support_contact?: SupportContact;
|
||||
};
|
||||
|
||||
export type TAgentsMap = Record<string, Agent | undefined>;
|
||||
|
|
@ -244,7 +250,13 @@ export type AgentCreateParams = {
|
|||
model_parameters: AgentModelParameters;
|
||||
} & Pick<
|
||||
Agent,
|
||||
'agent_ids' | 'end_after_tools' | 'hide_sequential_outputs' | 'artifacts' | 'recursion_limit'
|
||||
| 'agent_ids'
|
||||
| 'end_after_tools'
|
||||
| 'hide_sequential_outputs'
|
||||
| 'artifacts'
|
||||
| 'recursion_limit'
|
||||
| 'category'
|
||||
| 'support_contact'
|
||||
>;
|
||||
|
||||
export type AgentUpdateParams = {
|
||||
|
|
@ -263,15 +275,22 @@ export type AgentUpdateParams = {
|
|||
isCollaborative?: boolean;
|
||||
} & Pick<
|
||||
Agent,
|
||||
'agent_ids' | 'end_after_tools' | 'hide_sequential_outputs' | 'artifacts' | 'recursion_limit'
|
||||
| 'agent_ids'
|
||||
| 'end_after_tools'
|
||||
| 'hide_sequential_outputs'
|
||||
| 'artifacts'
|
||||
| 'recursion_limit'
|
||||
| 'category'
|
||||
| 'support_contact'
|
||||
>;
|
||||
|
||||
export type AgentListParams = {
|
||||
limit?: number;
|
||||
before?: string | null;
|
||||
after?: string | null;
|
||||
order?: 'asc' | 'desc';
|
||||
provider?: AgentProvider;
|
||||
requiredPermission: number;
|
||||
category?: string;
|
||||
search?: string;
|
||||
cursor?: string;
|
||||
promoted?: 0 | 1;
|
||||
};
|
||||
|
||||
export type AgentListResponse = {
|
||||
|
|
@ -280,6 +299,7 @@ export type AgentListResponse = {
|
|||
first_id: string;
|
||||
last_id: string;
|
||||
has_more: boolean;
|
||||
after?: string;
|
||||
};
|
||||
|
||||
export type AgentFile = {
|
||||
|
|
|
|||
|
|
@ -271,22 +271,7 @@ describe('AclEntry Model Tests', () => {
|
|||
const effective = await methods.getEffectivePermissions(principalsList, 'agent', resourceId);
|
||||
|
||||
/** Combined permissions should be VIEW | EDIT */
|
||||
expect(effective.effectiveBits).toBe(PermissionBits.VIEW | PermissionBits.EDIT);
|
||||
|
||||
/** Should have 2 sources */
|
||||
expect(effective.sources).toHaveLength(2);
|
||||
|
||||
/** Check sources */
|
||||
const userSource = effective.sources.find((s) => s.from === 'user');
|
||||
const groupSource = effective.sources.find((s) => s.from === 'group');
|
||||
|
||||
expect(userSource).toBeDefined();
|
||||
expect(userSource?.permBits).toBe(PermissionBits.VIEW);
|
||||
expect(userSource?.direct).toBe(true);
|
||||
|
||||
expect(groupSource).toBeDefined();
|
||||
expect(groupSource?.permBits).toBe(PermissionBits.EDIT);
|
||||
expect(groupSource?.direct).toBe(true);
|
||||
expect(effective).toBe(PermissionBits.VIEW | PermissionBits.EDIT);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -489,16 +474,15 @@ describe('AclEntry Model Tests', () => {
|
|||
inheritedFrom: projectId,
|
||||
});
|
||||
|
||||
/** Get effective permissions including sources */
|
||||
/** Get effective permissions */
|
||||
const effective = await methods.getEffectivePermissions(
|
||||
[{ principalType: 'user', principalId: userId }],
|
||||
'agent',
|
||||
childResourceId,
|
||||
);
|
||||
|
||||
expect(effective.sources).toHaveLength(1);
|
||||
expect(effective.sources[0].inheritedFrom?.toString()).toBe(projectId.toString());
|
||||
expect(effective.sources[0].direct).toBe(false);
|
||||
/** Should have VIEW permission from inherited entry */
|
||||
expect(effective).toBe(PermissionBits.VIEW);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -144,6 +144,66 @@ export function createAgentCategoryMethods(mongoose: typeof import('mongoose'))
|
|||
return await AgentCategory.find({}).sort({ order: 1, label: 1 }).lean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure default categories exist, seed them if none are present
|
||||
* @returns Promise<boolean> - true if categories were seeded, false if they already existed
|
||||
*/
|
||||
async function ensureDefaultCategories(): Promise<boolean> {
|
||||
const existingCategories = await getAllCategories();
|
||||
|
||||
if (existingCategories.length > 0) {
|
||||
return false; // Categories already exist
|
||||
}
|
||||
|
||||
const defaultCategories = [
|
||||
{
|
||||
value: 'general',
|
||||
label: 'General',
|
||||
description: 'General purpose agents for common tasks and inquiries',
|
||||
order: 0,
|
||||
},
|
||||
{
|
||||
value: 'hr',
|
||||
label: 'Human Resources',
|
||||
description: 'Agents specialized in HR processes, policies, and employee support',
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
value: 'rd',
|
||||
label: 'Research & Development',
|
||||
description: 'Agents focused on R&D processes, innovation, and technical research',
|
||||
order: 2,
|
||||
},
|
||||
{
|
||||
value: 'finance',
|
||||
label: 'Finance',
|
||||
description: 'Agents specialized in financial analysis, budgeting, and accounting',
|
||||
order: 3,
|
||||
},
|
||||
{
|
||||
value: 'it',
|
||||
label: 'IT',
|
||||
description: 'Agents for IT support, technical troubleshooting, and system administration',
|
||||
order: 4,
|
||||
},
|
||||
{
|
||||
value: 'sales',
|
||||
label: 'Sales',
|
||||
description: 'Agents focused on sales processes, customer relations.',
|
||||
order: 5,
|
||||
},
|
||||
{
|
||||
value: 'aftersales',
|
||||
label: 'After Sales',
|
||||
description: 'Agents specialized in post-sale support, maintenance, and customer service',
|
||||
order: 6,
|
||||
},
|
||||
];
|
||||
|
||||
await seedCategories(defaultCategories);
|
||||
return true; // Categories were seeded
|
||||
}
|
||||
|
||||
return {
|
||||
getActiveCategories,
|
||||
getCategoriesWithCounts,
|
||||
|
|
@ -155,6 +215,7 @@ export function createAgentCategoryMethods(mongoose: typeof import('mongoose'))
|
|||
deleteCategory,
|
||||
findCategoryById,
|
||||
getAllCategories,
|
||||
ensureDefaultCategories,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
9
packages/data-schemas/src/models/agentCategory.ts
Normal file
9
packages/data-schemas/src/models/agentCategory.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import agentCategorySchema from '~/schema/agentCategory';
|
||||
import type * as t from '~/types';
|
||||
|
||||
/**
|
||||
* Creates or returns the AgentCategory model using the provided mongoose instance and schema
|
||||
*/
|
||||
export function createAgentCategoryModel(mongoose: typeof import('mongoose')) {
|
||||
return mongoose.models.AgentCategory || mongoose.model<t.IAgentCategory>('AgentCategory', agentCategorySchema);
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import { createBalanceModel } from './balance';
|
|||
import { createConversationModel } from './convo';
|
||||
import { createMessageModel } from './message';
|
||||
import { createAgentModel } from './agent';
|
||||
import { createAgentCategoryModel } from './agentCategory';
|
||||
import { createRoleModel } from './role';
|
||||
import { createActionModel } from './action';
|
||||
import { createAssistantModel } from './assistant';
|
||||
|
|
@ -37,6 +38,7 @@ export function createModels(mongoose: typeof import('mongoose')) {
|
|||
Conversation: createConversationModel(mongoose),
|
||||
Message: createMessageModel(mongoose),
|
||||
Agent: createAgentModel(mongoose),
|
||||
AgentCategory: createAgentCategoryModel(mongoose),
|
||||
Role: createRoleModel(mongoose),
|
||||
Action: createActionModel(mongoose),
|
||||
Assistant: createAssistantModel(mongoose),
|
||||
|
|
|
|||
|
|
@ -102,7 +102,6 @@ const agentSchema = new Schema<IAgent>(
|
|||
type: {
|
||||
name: {
|
||||
type: String,
|
||||
minlength: [3, 'Support contact name must be at least 3 characters.'],
|
||||
trim: true,
|
||||
},
|
||||
email: {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
import { Schema, Document } from 'mongoose';
|
||||
|
||||
export interface IAgentCategory extends Document {
|
||||
value: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
order: number;
|
||||
isActive: boolean;
|
||||
}
|
||||
import type { IAgentCategory } from '~/types';
|
||||
|
||||
const agentCategorySchema = new Schema<IAgentCategory>(
|
||||
{
|
||||
|
|
@ -46,4 +39,4 @@ const agentCategorySchema = new Schema<IAgentCategory>(
|
|||
|
||||
agentCategorySchema.index({ isActive: 1, order: 1 });
|
||||
|
||||
export default agentCategorySchema;
|
||||
export default agentCategorySchema;
|
||||
|
|
|
|||
|
|
@ -36,10 +36,5 @@ export interface IAgent extends Omit<Document, 'model'> {
|
|||
versions?: Omit<IAgent, 'versions'>[];
|
||||
category: string;
|
||||
support_contact?: ISupportContact;
|
||||
category: string;
|
||||
support_contact?: {
|
||||
name?: string;
|
||||
email?: string;
|
||||
};
|
||||
is_promoted?: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export * from './balance';
|
|||
export * from './banner';
|
||||
export * from './message';
|
||||
export * from './agent';
|
||||
export * from './agentCategory';
|
||||
export * from './role';
|
||||
export * from './action';
|
||||
export * from './assistant';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue