LibreChat/client/src/components/SidePanel/Agents/ErrorDisplay.tsx
“Praneeth 949682ef0f
🏪 feat: Agent Marketplace
bugfix: Enhance Agent and AgentCategory schemas with new fields for category, support contact, and promotion status

refactored and moved agent category methods and schema to data-schema package

🔧 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

🔧 fix: Fixed agent actions access

feat: Add role-level permissions for agent sharing people picker

  - Add PEOPLE_PICKER permission type with VIEW_USERS and VIEW_GROUPS permissions
  - Create custom middleware for query-aware permission validation
  - Implement permission-based type filtering in PeoplePicker component
  - Hide people picker UI when user lacks permissions, show only public toggle
  - Support granular access: users-only, groups-only, or mixed search modes

refactor: Replace marketplace interface config with permission-based system

  - Add MARKETPLACE permission type to handle marketplace access control
  - Update interface configuration to use role-based marketplace settings (admin/user)
  - Replace direct marketplace boolean config with permission-based checks
  - Modify frontend components to use marketplace permissions instead of interface config
  - Update agent query hooks to use marketplace permissions for determining permission levels
  - Add marketplace configuration structure similar to peoplePicker in YAML config
  - Backend now sets MARKETPLACE permissions based on interface configuration
  - When marketplace enabled: users get agents with EDIT permissions in dropdown lists  (builder mode)
  - When marketplace disabled: users get agents with VIEW permissions  in dropdown lists (browse mode)

🔧 fix: Redirect to New Chat if No Marketplace Access and Required Agent Name Placeholder (#8213)

* Fix: Fix the redirect to new chat page if access to marketplace is denied

* Fixed the required agent name placeholder

---------

Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com>

chore: fix tests, remove unnecessary imports

refactor: Implement permission checks for file access via agents

- Updated `hasAccessToFilesViaAgent` to utilize permission checks for VIEW and EDIT access.
- Replaced project-based access validation with permission-based checks.
- Enhanced tests to cover new permission logic and ensure proper access control for files associated with agents.
- Cleaned up imports and initialized models in test files for consistency.

refactor: Enhance test setup and cleanup for file access control

- Introduced modelsToCleanup array to track models added during tests for proper cleanup.
- Updated afterAll hooks in test files to ensure all collections are cleared and only added models are deleted.
- Improved consistency in model initialization across test files.
- Added comments for clarity on cleanup processes and test data management.

chore: Update Jest configuration and test setup for improved timeout handling

- Added a global test timeout of 30 seconds in jest.config.js.
- Configured jest.setTimeout in jestSetup.js to allow individual test overrides if needed.
- Enhanced test reliability by ensuring consistent timeout settings across all tests.

refactor: Implement file access filtering based on agent permissions

- Introduced `filterFilesByAgentAccess` function to filter files based on user access through agents.
- Updated `getFiles` and `primeFiles` functions to utilize the new filtering logic.
- Moved `hasAccessToFilesViaAgent` function from the File model to permission services, adjusting imports accordingly
- Enhanced tests to ensure proper access control and filtering behavior for files associated with agents.

fix: make support_contact field a nested object rather than a sub-document

refactor: Update support_contact field initialization in agent model

- Removed handling for empty support_contact object in createAgent function.
- Changed default value of support_contact in agent schema to undefined.

test: Add comprehensive tests for support_contact field handling and versioning

refactor: remove unused avatar upload mutation field and add informational toast for success

chore: add missing SidePanelProvider for AgentMarketplace and organize imports

fix: resolve agent selection race condition in marketplace HandleStartChat
- Set agent in localStorage before newConversation to prevent useSelectorEffects from auto-selecting previous agent

fix: resolve agent dropdown showing raw ID instead of agent info from URL

  - Add proactive agent fetching when agent_id is present in URL parameters
  - Inject fetched agent into agents cache so dropdowns display proper name/avatar
  - Use useAgentsMap dependency to ensure proper cache initialization timing
  - Prevents raw agent IDs from showing in UI when visiting shared agent links

Fix: Agents endpoint renamed to "My Agent" for less confusion with the Marketplace agents.

chore: fix ESLint issues and Test Mocks

ci: update permissions structure in loadDefaultInterface tests

- Refactored permissions for MEMORY and added new permissions for MARKETPLACE and PEOPLE_PICKER.
- Ensured consistent structure for permissions across different types.

feat:  support_contact validation to allow empty email strings
2025-08-13 16:24:18 -04:00

251 lines
7.7 KiB
TypeScript

import React from 'react';
import { Button } from '@librechat/client';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
// Comprehensive error type that handles all possible error structures
type ApiError =
| string
| Error
| {
message?: string;
status?: number;
code?: string;
response?: {
data?: {
userMessage?: string;
suggestion?: string;
message?: string;
};
status?: number;
};
data?: {
userMessage?: string;
suggestion?: string;
message?: string;
};
};
interface ErrorDisplayProps {
error: ApiError;
onRetry?: () => void;
context?: {
searchQuery?: string;
category?: string;
};
}
/**
* User-friendly error display component with actionable suggestions
*/
export const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ error, onRetry, context }) => {
const localize = useLocalize();
// Type guards
const isErrorObject = (err: ApiError): err is { [key: string]: unknown } => {
return typeof err === 'object' && err !== null && !(err instanceof Error);
};
const isErrorInstance = (err: ApiError): err is Error => {
return err instanceof Error;
};
// Extract user-friendly error information
const getErrorInfo = (): { title: string; message: string; suggestion: string } => {
// Handle different error types
let errorData: unknown;
if (typeof error === 'string') {
errorData = { message: error };
} else if (isErrorInstance(error)) {
errorData = { message: error.message };
} else if (isErrorObject(error)) {
// Handle axios error response structure
errorData = (error as any)?.response?.data || (error as any)?.data || error;
} else {
errorData = error;
}
// Handle network errors first
let errorMessage = '';
if (isErrorInstance(error)) {
errorMessage = error.message;
} else if (isErrorObject(error) && (error as any)?.message) {
errorMessage = (error as any).message;
}
const errorCode = isErrorObject(error) ? (error as any)?.code : '';
// Handle timeout errors specifically
if (errorCode === 'ECONNABORTED' || errorMessage?.includes('timeout')) {
return {
title: localize('com_agents_error_timeout_title'),
message: localize('com_agents_error_timeout_message'),
suggestion: localize('com_agents_error_timeout_suggestion'),
};
}
if (errorCode === 'NETWORK_ERROR' || errorMessage?.includes('Network Error')) {
return {
title: localize('com_agents_error_network_title'),
message: localize('com_agents_error_network_message'),
suggestion: localize('com_agents_error_network_suggestion'),
};
}
// Handle specific HTTP status codes before generic userMessage
const status = isErrorObject(error) ? (error as any)?.response?.status : null;
if (status) {
if (status === 404) {
return {
title: localize('com_agents_error_not_found_title'),
message: getNotFoundMessage(),
suggestion: localize('com_agents_error_not_found_suggestion'),
};
}
if (status === 400) {
return {
title: localize('com_agents_error_invalid_request'),
message:
(errorData as any)?.userMessage || localize('com_agents_error_bad_request_message'),
suggestion:
(errorData as any)?.suggestion || localize('com_agents_error_bad_request_suggestion'),
};
}
if (status >= 500) {
return {
title: localize('com_agents_error_server_title'),
message: localize('com_agents_error_server_message'),
suggestion: localize('com_agents_error_server_suggestion'),
};
}
}
// Use user-friendly message from backend if available (after specific status code handling)
if (errorData && typeof errorData === 'object' && (errorData as any)?.userMessage) {
return {
title: getContextualTitle(),
message: (errorData as any).userMessage,
suggestion:
(errorData as any).suggestion || localize('com_agents_error_suggestion_generic'),
};
}
// Fallback to generic error with contextual title
return {
title: getContextualTitle(),
message: localize('com_agents_error_generic'),
suggestion: localize('com_agents_error_suggestion_generic'),
};
};
/**
* Get contextual title based on current operation
*/
const getContextualTitle = (): string => {
if (context?.searchQuery) {
return localize('com_agents_error_search_title');
}
if (context?.category) {
return localize('com_agents_error_category_title');
}
return localize('com_agents_error_title');
};
/**
* Get context-specific not found message
*/
const getNotFoundMessage = (): string => {
if (context?.searchQuery) {
return localize('com_agents_search_no_results', { query: context.searchQuery });
}
if (context?.category && context.category !== 'all') {
return localize('com_agents_category_empty', { category: context.category });
}
return localize('com_agents_error_not_found_message');
};
const { title, message, suggestion } = getErrorInfo();
return (
<div className="py-12 text-center" role="alert" aria-live="assertive" aria-atomic="true">
<div className="mx-auto max-w-md space-y-4">
{/* Error icon with proper accessibility */}
<div className="flex justify-center">
<div
className={cn(
'flex h-12 w-12 items-center justify-center rounded-full',
'bg-red-100 dark:bg-red-900/20',
)}
>
<svg
className="h-6 w-6 text-red-600 dark:text-red-400"
fill="none"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="currentColor"
aria-hidden="true"
role="img"
aria-label="Error icon"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
</div>
</div>
{/* Error content with proper headings and structure */}
<div className="space-y-3">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white" id="error-title">
{title}
</h3>
<p
className="text-gray-600 dark:text-gray-400"
id="error-message"
aria-describedby="error-title"
>
{message}
</p>
<p
className="text-sm text-gray-500 dark:text-gray-500"
id="error-suggestion"
role="note"
aria-label={`Suggestion: ${suggestion}`}
>
💡 {suggestion}
</p>
</div>
{/* Retry button with enhanced accessibility */}
{onRetry && (
<div className="pt-2">
<Button
onClick={onRetry}
variant="outline"
size="sm"
className={cn(
'border-red-300 text-red-700 hover:bg-red-50 focus:ring-2 focus:ring-red-500',
'dark:border-red-600 dark:text-red-400 dark:hover:bg-red-900/20 dark:focus:ring-red-400',
)}
aria-describedby="error-message error-suggestion"
aria-label={`Retry action. ${message}`}
>
{localize('com_agents_error_retry')}
</Button>
</div>
)}
</div>
</div>
);
};
export default ErrorDisplay;