Add pending extension indicator to extension panel (#5493)

Signed-off-by: Alex Holder <alexeeyre@gmail.com>
This commit is contained in:
Alex Holder
2025-11-06 04:30:34 +00:00
committed by GitHub
parent 6209a81e1c
commit ec6faf92a3
3 changed files with 47 additions and 1 deletions
@@ -48,11 +48,22 @@ export default function ExtensionsSection({
const [showEnvVarsStateVar, setShowEnvVarsStateVar] = useState<boolean | undefined | null>(
showEnvVars
);
const [pendingActivationExtensions, setPendingActivationExtensions] = useState<Set<string>>(
new Set()
);
// Update deep link state when props change
useEffect(() => {
setDeepLinkConfigStateVar(deepLinkConfig);
setShowEnvVarsStateVar(showEnvVars);
if (deepLinkConfig && !showEnvVars) {
setPendingActivationExtensions((prev) => {
const updated = new Set(prev);
updated.add(deepLinkConfig.name);
return updated;
});
}
}, [deepLinkConfig, showEnvVars]);
// Process extensions from context - this automatically updates when extensionsList changes
@@ -102,6 +113,12 @@ export default function ExtensionsSection({
sessionId: sessionId,
});
setPendingActivationExtensions((prev) => {
const updated = new Set(prev);
updated.delete(extensionConfig.name);
return updated;
});
await fetchExtensions();
return true;
};
@@ -122,8 +139,21 @@ export default function ExtensionsSection({
extensionConfig: extensionConfig,
sessionId: sessionId,
});
setPendingActivationExtensions((prev) => {
const updated = new Set(prev);
updated.delete(extensionConfig.name);
return updated;
});
} catch (error) {
console.error('Failed to activate extension:', error);
// If activation fails, mark as pending if it's enabled in config
if (formData.enabled) {
setPendingActivationExtensions((prev) => {
const updated = new Set(prev);
updated.add(extensionConfig.name);
return updated;
});
}
} finally {
await fetchExtensions();
if (onModalClose) {
@@ -202,6 +232,7 @@ export default function ExtensionsSection({
onConfigure={handleConfigureClick}
disableConfiguration={disableConfiguration}
searchTerm={searchTerm}
pendingActivationExtensions={pendingActivationExtensions}
/>
{!hideButtons && (
@@ -11,6 +11,7 @@ interface ExtensionItemProps {
onToggle: (extension: FixedExtensionEntry) => Promise<boolean | void> | void;
onConfigure?: (extension: FixedExtensionEntry) => void;
isStatic?: boolean; // to not allow users to edit configuration
isPendingActivation?: boolean;
}
export default function ExtensionItem({
@@ -18,6 +19,7 @@ export default function ExtensionItem({
onToggle,
onConfigure,
isStatic,
isPendingActivation = false,
}: ExtensionItemProps) {
// Add local state to track the visual toggle state
const [visuallyEnabled, setVisuallyEnabled] = useState(extension.enabled);
@@ -79,7 +81,17 @@ export default function ExtensionItem({
onClick={() => handleToggle(extension)}
>
<CardHeader>
<CardTitle className="">{getFriendlyTitle(extension)}</CardTitle>
<CardTitle className="flex items-center gap-2">
{getFriendlyTitle(extension)}
{isPendingActivation && (
<span
className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400 border border-amber-300 dark:border-amber-700"
title="Extension will be activated when you start a new chat session"
>
Pending
</span>
)}
</CardTitle>
<CardAction onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-end gap-2">
@@ -11,6 +11,7 @@ interface ExtensionListProps {
isStatic?: boolean;
disableConfiguration?: boolean;
searchTerm?: string;
pendingActivationExtensions?: Set<string>;
}
export default function ExtensionList({
@@ -20,6 +21,7 @@ export default function ExtensionList({
isStatic,
disableConfiguration: _disableConfiguration,
searchTerm = '',
pendingActivationExtensions = new Set(),
}: ExtensionListProps) {
const matchesSearch = (extension: FixedExtensionEntry): boolean => {
if (!searchTerm) return true;
@@ -63,6 +65,7 @@ export default function ExtensionList({
onToggle={onToggle}
onConfigure={onConfigure}
isStatic={isStatic}
isPendingActivation={pendingActivationExtensions.has(extension.name)}
/>
))}
</div>