From c52a95d565ecb3996bff09e229af4081446332b9 Mon Sep 17 00:00:00 2001 From: Bradley Axen Date: Fri, 11 Apr 2025 17:27:17 -0700 Subject: [PATCH] feat: update extensions cards (#2174) --- .../extensions/ExtensionsSection.tsx | 15 +++++- .../subcomponents/ExtensionItem.tsx | 19 ++++---- .../subcomponents/ExtensionList.tsx | 46 ++++++++++++++----- 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx b/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx index c3f81064..c8083e4d 100644 --- a/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx +++ b/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx @@ -37,7 +37,20 @@ export default function ExtensionsSection({ deepLinkConfig, showEnvVars }: Exten const fetchExtensions = useCallback(async () => { const extensionsList = await getExtensions(true); // Force refresh // Sort extensions by name to maintain consistent order - const sortedExtensions = [...extensionsList].sort((a, b) => a.name.localeCompare(b.name)); + const sortedExtensions = [...extensionsList].sort((a, b) => { + // First sort by builtin + if (a.type === 'builtin' && b.type !== 'builtin') return -1; + if (a.type !== 'builtin' && b.type === 'builtin') return 1; + + // Then sort by bundled (handle null/undefined cases) + const aBundled = a.bundled === true; + const bBundled = b.bundled === true; + if (aBundled && !bBundled) return -1; + if (!aBundled && bBundled) return 1; + + // Finally sort alphabetically within each group + return a.name.localeCompare(b.name); + }); setExtensions(sortedExtensions); }, [getExtensions]); diff --git a/ui/desktop/src/components/settings_v2/extensions/subcomponents/ExtensionItem.tsx b/ui/desktop/src/components/settings_v2/extensions/subcomponents/ExtensionItem.tsx index c025f751..9f99be11 100644 --- a/ui/desktop/src/components/settings_v2/extensions/subcomponents/ExtensionItem.tsx +++ b/ui/desktop/src/components/settings_v2/extensions/subcomponents/ExtensionItem.tsx @@ -46,14 +46,15 @@ export default function ExtensionItem({ extension, onToggle, onConfigure }: Exte } }, [extension.enabled, isToggling]); - const renderFormattedSubtitle = () => { - const subtitle = getSubtitle(extension); - return subtitle.split('\n').map((part, index) => ( - - {index === 0 ? part : {part}} - {index < subtitle.split('\n').length - 1 &&
} -
- )); + const renderSubtitle = () => { + const { description, command } = getSubtitle(extension); + return ( + <> + {description && {description}} + {description && command &&
} + {command && {command}} + + ); }; // Bundled extensions and builtins are not editable @@ -67,7 +68,7 @@ export default function ExtensionItem({ extension, onToggle, onConfigure }: Exte >

{getFriendlyTitle(extension)}

-

{renderFormattedSubtitle()}

+

{renderSubtitle()}

); } + // Helper functions // Helper function to get a friendly title from extension name export function getFriendlyTitle(extension: FixedExtensionEntry): string { @@ -46,23 +47,46 @@ export function getFriendlyTitle(extension: FixedExtensionEntry): string { .join(' '); } +export interface SubtitleParts { + description: string | null; + command: string | null; +} + // Helper function to get a subtitle based on extension type and configuration -export function getSubtitle(config: ExtensionConfig): string { +export function getSubtitle(config: ExtensionConfig): SubtitleParts { if (config.type === 'builtin') { // Find matching extension in the data - const extensionData = builtInExtensionsData.find((ext) => ext.name === config.name); - if (extensionData?.description) { - return extensionData.description; - } - return 'Built-in extension'; + const extensionData = builtInExtensionsData.find( + (ext) => + ext.name.toLowerCase().replace(/\s+/g, '') === config.name.toLowerCase().replace(/\s+/g, '') + ); + return { + description: extensionData?.description || 'Built-in extension', + command: null, + }; } + if (config.type === 'stdio') { - // remove shims for displaying - const full_command = combineCmdAndArgs(removeShims(config.cmd), config.args); - return `STDIO extension${config.description ? `: ${config.description}` : ''}${full_command ? `\n${full_command}` : ''}`; + // Only include command if it exists + const full_command = config.cmd + ? combineCmdAndArgs(removeShims(config.cmd), config.args) + : null; + return { + description: config.description || null, + command: full_command, + }; } + if (config.type === 'sse') { - return `SSE extension${config.description ? `: ${config.description}` : ''}${config.uri ? ` (${config.uri})` : ''}`; + const description = config.description + ? `SSE extension: ${config.description}` + : 'SSE extension'; + const command = config.uri || null; + return { description, command }; } - return `Unknown type of extension`; + + return { + description: 'Unknown type of extension', + command: null, + }; }