2025-04-07 19:16:56 -04:00
|
|
|
import React, { useRef } from 'react';
|
|
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectArrow,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectItemCheck,
|
|
|
|
|
SelectLabel,
|
|
|
|
|
SelectPopover,
|
|
|
|
|
SelectProvider,
|
|
|
|
|
} from '@ariakit/react';
|
|
|
|
|
import { cn } from '~/utils';
|
|
|
|
|
|
|
|
|
|
interface MultiSelectProps<T extends string> {
|
|
|
|
|
items: T[];
|
|
|
|
|
label?: string;
|
|
|
|
|
placeholder?: string;
|
|
|
|
|
defaultSelectedValues?: T[];
|
|
|
|
|
onSelectedValuesChange?: (values: T[]) => void;
|
|
|
|
|
renderSelectedValues?: (values: T[], placeholder?: string) => React.ReactNode;
|
|
|
|
|
className?: string;
|
|
|
|
|
itemClassName?: string;
|
|
|
|
|
labelClassName?: string;
|
|
|
|
|
selectClassName?: string;
|
|
|
|
|
selectIcon?: React.ReactNode;
|
|
|
|
|
popoverClassName?: string;
|
|
|
|
|
selectItemsClassName?: string;
|
|
|
|
|
selectedValues: T[];
|
|
|
|
|
setSelectedValues: (values: T[]) => void;
|
2025-06-19 18:27:55 -04:00
|
|
|
renderItemContent?: (
|
|
|
|
|
value: T,
|
|
|
|
|
defaultContent: React.ReactNode,
|
|
|
|
|
isSelected: boolean,
|
|
|
|
|
) => React.ReactNode;
|
2025-04-07 19:16:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function defaultRender<T extends string>(values: T[], placeholder?: string) {
|
|
|
|
|
if (values.length === 0) {
|
|
|
|
|
return placeholder || 'Select...';
|
|
|
|
|
}
|
|
|
|
|
if (values.length === 1) {
|
|
|
|
|
return values[0];
|
|
|
|
|
}
|
|
|
|
|
return `${values.length} items selected`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function MultiSelect<T extends string>({
|
|
|
|
|
items,
|
|
|
|
|
label,
|
|
|
|
|
placeholder = 'Select...',
|
|
|
|
|
defaultSelectedValues = [],
|
|
|
|
|
onSelectedValuesChange,
|
|
|
|
|
renderSelectedValues = defaultRender,
|
|
|
|
|
className,
|
|
|
|
|
selectIcon,
|
|
|
|
|
itemClassName,
|
|
|
|
|
labelClassName,
|
|
|
|
|
selectClassName,
|
|
|
|
|
popoverClassName,
|
|
|
|
|
selectItemsClassName,
|
|
|
|
|
selectedValues = [],
|
|
|
|
|
setSelectedValues,
|
2025-06-19 18:27:55 -04:00
|
|
|
renderItemContent,
|
2025-04-07 19:16:56 -04:00
|
|
|
}: MultiSelectProps<T>) {
|
|
|
|
|
const selectRef = useRef<HTMLButtonElement>(null);
|
|
|
|
|
|
|
|
|
|
const handleValueChange = (values: T[]) => {
|
|
|
|
|
setSelectedValues(values);
|
|
|
|
|
if (onSelectedValuesChange) {
|
|
|
|
|
onSelectedValuesChange(values);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
2025-04-09 16:11:16 -04:00
|
|
|
<div className={className}>
|
2025-04-07 19:16:56 -04:00
|
|
|
<SelectProvider value={selectedValues} setValue={handleValueChange}>
|
|
|
|
|
{label && (
|
|
|
|
|
<SelectLabel className={cn('mb-1 block text-sm text-text-primary', labelClassName)}>
|
|
|
|
|
{label}
|
|
|
|
|
</SelectLabel>
|
|
|
|
|
)}
|
|
|
|
|
<Select
|
|
|
|
|
ref={selectRef}
|
|
|
|
|
className={cn(
|
|
|
|
|
'flex items-center justify-between gap-2 rounded-xl px-3 py-2 text-sm',
|
|
|
|
|
'bg-surface-tertiary text-text-primary shadow-sm hover:cursor-pointer hover:bg-surface-hover',
|
|
|
|
|
'outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75',
|
|
|
|
|
selectClassName,
|
|
|
|
|
selectedValues.length > 0 && selectItemsClassName != null && selectItemsClassName,
|
|
|
|
|
)}
|
2025-04-09 16:11:16 -04:00
|
|
|
onChange={(e) => e.stopPropagation()}
|
2025-04-07 19:16:56 -04:00
|
|
|
>
|
|
|
|
|
{selectIcon && selectIcon}
|
2025-04-09 16:11:16 -04:00
|
|
|
<span className="mr-auto hidden truncate md:block">
|
2025-04-07 19:16:56 -04:00
|
|
|
{renderSelectedValues(selectedValues, placeholder)}
|
|
|
|
|
</span>
|
|
|
|
|
<SelectArrow className="ml-1 hidden stroke-1 text-base opacity-75 md:block" />
|
|
|
|
|
</Select>
|
|
|
|
|
<SelectPopover
|
|
|
|
|
gutter={4}
|
|
|
|
|
sameWidth
|
|
|
|
|
modal
|
|
|
|
|
unmountOnHide
|
|
|
|
|
finalFocus={selectRef}
|
|
|
|
|
className={cn(
|
|
|
|
|
'animate-popover z-50 flex max-h-[300px]',
|
|
|
|
|
'flex-col overflow-auto overscroll-contain rounded-xl',
|
|
|
|
|
'bg-surface-secondary px-1.5 py-1 text-text-primary shadow-lg',
|
|
|
|
|
'border border-border-light',
|
|
|
|
|
'outline-none',
|
|
|
|
|
popoverClassName,
|
|
|
|
|
)}
|
|
|
|
|
>
|
2025-06-19 18:27:55 -04:00
|
|
|
{items.map((value) => {
|
|
|
|
|
const defaultContent = (
|
|
|
|
|
<>
|
🔌 feat: MCP Reinitialization and OAuth in UI (#8598)
* ✨ feat: Add connection status endpoint for MCP servers
- Implemented a new endpoint to retrieve the connection status of all MCP servers without disconnecting idle connections.
- Enhanced MCPManager class with a method to get all user-specific connections.
* feat: add silencer arg to loadCustomConfig function to conditionally print config details
- Modified loadCustomConfig to accept a printConfig parameter that allows me to prevent the entire custom config being printed every time it is called
* fix: new status endpoint actually works now, changes to manager.ts to support it
- Updated the connection status endpoint to utilize Maps for app and user connections, rather than incorrectly treating them as objects.
- Introduced a new method + variable in MCPManager to track servers requiring OAuth discovered at startup.
- Stopped OAuth flow from continuing once detected during startup for a new connection
* refactor: Remove hasAuthConfig since we can get that on the frontend without needing to use the endpoint
* feat: Add MCP connection status query and query key for new endpoint
- Introduced a new query hook `useMCPConnectionStatusQuery` to fetch the connection status of MCP servers.
- Added request in data-service
- Defined the API endpoint for retrieving MCP connection status in api-endpoints.ts.
- Defined new types for MCP connection status responses in the types module.
- Added mcpConnectionStatus key
* feat: Enhance MCPSelect component with connection status and server configuration
- Added connection status handling for MCP servers using the new `useMCPConnectionStatusQuery` hook.
- Implemented logic to display appropriate status icons based on connection state and authentication configuration.
- Updated the server selection logic to utilize configured MCP servers from the startup configuration.
- Refactored the rendering of configuration buttons and status indicators for improved user interaction.
* refactor: move MCPConfigDialog to its own MCP subdir in ui and update import
* refactor: silence loadCustomConfig in status endpoint
* feat: Add optional pluginKey parameter to getUserPluginAuthValue
* feat: Add MCP authentication values endpoint and related queries
- Implemented a new endpoint to check authentication value flags for specific MCP servers, returning boolean indicators for each custom user variable.
- Added a corresponding query hook `useMCPAuthValuesQuery` to fetch authentication values from the frontend.
- Defined the API endpoint for retrieving MCP authentication values in api-endpoints.ts.
- Updated data-service to include a method for fetching MCP authentication values.
- Introduced new types for MCP authentication values responses in the types module.
- Added a new query key for MCP authentication values.
* feat: Localize MCPSelect component status labels and aria attributes
- Updated the MCPSelect component to use localized strings for connection status labels and aria attributes, enhancing accessibility and internationalization support.
- Added new translation keys for various connection states in the translation.json file.
* feat: Implement filtered MCP values selection based on connection status in MCPSelect
- Added a new `filteredSetMCPValues` function to ensure only connected servers are selectable in the MCPSelect component.
- Updated the rendering logic to visually indicate the connection status of servers by adjusting opacity.
- Enhanced accessibility by localizing the aria-label for the configuration button.
* feat: Add CustomUserVarsSection component for managing user variables
- Introduced a new `CustomUserVarsSection` component to allow users to configure custom variables for MCP servers.
- Integrated localization for user interface elements and added new translation keys for variable management.
- Added functionality to save and revoke user variables, with visual indicators for set/unset states.
* feat: Enhance MCPSelect and MCPConfigDialog with improved state management and UI updates
- Integrated `useQueryClient` to refetch queries for tools, authentication values, and connection status upon successful plugin updates in MCPSelect.
- Simplified plugin key handling by directly using the formatted plugin key in save and revoke operations.
- Updated MCPConfigDialog to include server status indicators and improved dialog content structure for better user experience.
- Added new translation key for active status in the localization files.
* feat: Enhance MCPConfigDialog with dynamic server status badges and localization updates
- Added a helper function to render status badges based on the connection state of the MCP server, improving user feedback on connection status.
- Updated the localization files to include new translation keys for connection states such as "Connecting" and "Offline".
- Refactored the dialog to utilize the new status rendering function for better code organization and readability.
* feat: Implement OAuth handling and server initialization in MCP reinitialize flow
- Added OAuth handling to the MCP reinitialize endpoint, allowing the server to capture and return OAuth URLs when required.
- Updated the MCPConfigDialog to include a new ServerInitializationSection for managing server initialization and OAuth flow.
- Enhanced the user experience by providing feedback on server status and OAuth requirements through localized messages.
- Introduced new translation keys for OAuth-related messages in the localization files.
- Refactored the MCPSelect component to remove unused authentication configuration props.
* feat: Make OAuth actually work / update after OAuth link authorized
- Improved the handling of OAuth flows in the MCP reinitialize process, allowing for immediate return when OAuth is initiated.
- Updated the UserController to extract server names from plugin keys for better logging and connection management.
- Enhanced the MCPSelect component to reflect authentication status based on OAuth requirements.
- Implemented polling for OAuth completion in the ServerInitializationSection to improve user feedback during the connection process.
- Refactored MCPManager to support new OAuth flow initiation logic and connection handling.
* refactor: Simplify MCPPanel component and enhance server status display
- Removed unused imports and state management related to user plugins and server reinitialization.
- Integrated connection status handling directly into the MCPPanel for improved user feedback.
- Updated the rendering logic to display server connection states with visual indicators.
- Refactored the editing view to utilize new components for server initialization and custom user variables management.
* chore: remove comments
* chore: remove unused translation key for MCP panel
* refactor: Rename returnOnOAuthInitiated to returnOnOAuth for clarity
* refactor: attempt initialize on server click
* feat: add cancel OAuth flow functionality and related UI updates
* refactor: move server status icon logic into its own component
* chore: remove old localization strings (makes more sense for icon labels to just use configure stirng since thats where it leads to)
* fix: fix accessibility issues with MCPSelect
* fix: add missing save/revoke mutation logic to MCPPanel
* styling: add margin to checkmark in MultiSelect
* fix: add back in customUserVars check to hide gear config icon for servers without customUserVars
---------
Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
Co-authored-by: Dustin Healy <54083382+dustinhealy@users.noreply.github.com>
2025-07-22 22:52:45 -04:00
|
|
|
<SelectItemCheck className="mr-0.5 text-primary" />
|
2025-06-19 18:27:55 -04:00
|
|
|
<span className="truncate">{value}</span>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
const isCurrentItemSelected = selectedValues.includes(value);
|
|
|
|
|
return (
|
|
|
|
|
<SelectItem
|
|
|
|
|
key={value}
|
|
|
|
|
value={value}
|
|
|
|
|
className={cn(
|
|
|
|
|
'flex items-center gap-2 rounded-lg px-2 py-1.5 hover:cursor-pointer',
|
|
|
|
|
'scroll-m-1 outline-none transition-colors',
|
|
|
|
|
'hover:bg-black/[0.075] dark:hover:bg-white/10',
|
|
|
|
|
'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10',
|
|
|
|
|
'w-full min-w-0 text-sm',
|
|
|
|
|
itemClassName,
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{renderItemContent
|
|
|
|
|
? renderItemContent(value, defaultContent, isCurrentItemSelected)
|
|
|
|
|
: defaultContent}
|
|
|
|
|
</SelectItem>
|
|
|
|
|
);
|
|
|
|
|
})}
|
2025-04-07 19:16:56 -04:00
|
|
|
</SelectPopover>
|
|
|
|
|
</SelectProvider>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|