mirror of
https://github.com/aljazceru/goose.git
synced 2026-01-20 06:44:25 +01:00
Mini agent extension config (#2209)
Co-authored-by: Zaki Ali <zaki@squareup.com>
This commit is contained in:
@@ -7,9 +7,10 @@ import Back from './icons/Back';
|
||||
import { Bars } from './icons/Bars';
|
||||
import { Geese } from './icons/Geese';
|
||||
import Copy from './icons/Copy';
|
||||
import Check from './icons/Check';
|
||||
import { useConfig } from '../components/ConfigContext';
|
||||
import { settingsV2Enabled } from '../flags';
|
||||
import { useConfig } from './ConfigContext';
|
||||
import { FixedExtensionEntry } from './ConfigContext';
|
||||
import ExtensionList from './settings_v2/extensions/subcomponents/ExtensionList';
|
||||
import { Check } from 'lucide-react';
|
||||
|
||||
interface RecipeEditorProps {
|
||||
config?: Recipe;
|
||||
@@ -28,47 +29,83 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
const [description, setDescription] = useState(config?.description || '');
|
||||
const [instructions, setInstructions] = useState(config?.instructions || '');
|
||||
const [activities, setActivities] = useState<string[]>(config?.activities || []);
|
||||
const [availableExtensions, setAvailableExtensions] = useState<FullExtensionConfig[]>([]);
|
||||
const [selectedExtensions, setSelectedExtensions] = useState<string[]>(
|
||||
config?.extensions?.map((e) => e.id) || []
|
||||
);
|
||||
const [newActivity, setNewActivity] = useState('');
|
||||
const [extensionOptions, setExtensionOptions] = useState<FixedExtensionEntry[]>([]);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [extensionsLoaded, setExtensionsLoaded] = useState(false);
|
||||
|
||||
// Initialize selected extensions for the recipe from config or localStorage
|
||||
const [recipeExtensions, setRecipeExtensions] = useState<string[]>(() => {
|
||||
// First try to get from localStorage
|
||||
const stored = localStorage.getItem('recipe_editor_extensions');
|
||||
if (stored) {
|
||||
try {
|
||||
const parsed = JSON.parse(stored);
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch (e) {
|
||||
console.error('Failed to parse localStorage recipe extensions:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
// Fall back to config if available, using extension names
|
||||
const exts = [];
|
||||
return exts;
|
||||
});
|
||||
const [newActivity, setNewActivity] = useState('');
|
||||
|
||||
// Section visibility state
|
||||
const [activeSection, setActiveSection] = useState<
|
||||
'none' | 'activities' | 'instructions' | 'extensions'
|
||||
>('none');
|
||||
|
||||
// Load extensions
|
||||
// Load extensions when component mounts and when switching to extensions section
|
||||
useEffect(() => {
|
||||
const loadExtensions = async () => {
|
||||
if (settingsV2Enabled) {
|
||||
if (activeSection === 'extensions' && !extensionsLoaded) {
|
||||
const loadExtensions = async () => {
|
||||
try {
|
||||
const extensions = await getExtensions(false); // force refresh to get latest
|
||||
console.log('extensions {}', extensions);
|
||||
setAvailableExtensions(extensions || []);
|
||||
console.log('Loading extensions for recipe editor');
|
||||
|
||||
if (extensions && extensions.length > 0) {
|
||||
// Map the extensions with the current selection state from recipeExtensions
|
||||
const initializedExtensions = extensions.map((ext) => ({
|
||||
...ext,
|
||||
enabled: recipeExtensions.includes(ext.name),
|
||||
}));
|
||||
|
||||
setExtensionOptions(initializedExtensions);
|
||||
setExtensionsLoaded(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load extensions:', error);
|
||||
}
|
||||
} else {
|
||||
const userSettingsStr = localStorage.getItem('user_settings');
|
||||
if (userSettingsStr) {
|
||||
const userSettings = JSON.parse(userSettingsStr);
|
||||
setAvailableExtensions(userSettings.extensions || []);
|
||||
}
|
||||
}
|
||||
};
|
||||
loadExtensions();
|
||||
// Intentionally omitting getExtensions from deps to avoid refresh loops
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
};
|
||||
loadExtensions();
|
||||
}
|
||||
}, [activeSection, getExtensions, recipeExtensions, extensionsLoaded]);
|
||||
|
||||
// Effect for updating extension options when recipeExtensions change
|
||||
useEffect(() => {
|
||||
if (extensionsLoaded && extensionOptions.length > 0) {
|
||||
const updatedOptions = extensionOptions.map((ext) => ({
|
||||
...ext,
|
||||
enabled: recipeExtensions.includes(ext.name),
|
||||
}));
|
||||
setExtensionOptions(updatedOptions);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [recipeExtensions, extensionsLoaded]);
|
||||
|
||||
const handleExtensionToggle = (extension: FixedExtensionEntry) => {
|
||||
console.log('Toggling extension:', extension.name);
|
||||
setRecipeExtensions((prev) => {
|
||||
const isSelected = prev.includes(extension.name);
|
||||
const newState = isSelected
|
||||
? prev.filter((extName) => extName !== extension.name)
|
||||
: [...prev, extension.name];
|
||||
|
||||
// Persist to localStorage
|
||||
localStorage.setItem('recipe_editor_extensions', JSON.stringify(newState));
|
||||
|
||||
const handleExtensionToggle = (id: string) => {
|
||||
console.log('Toggling extension:', id);
|
||||
setSelectedExtensions((prev) => {
|
||||
const isSelected = prev.includes(id);
|
||||
const newState = isSelected ? prev.filter((extId) => extId !== id) : [...prev, id];
|
||||
return newState;
|
||||
});
|
||||
};
|
||||
@@ -86,8 +123,8 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
|
||||
const getCurrentConfig = (): Recipe => {
|
||||
console.log('Creating config with:', {
|
||||
selectedExtensions,
|
||||
availableExtensions,
|
||||
selectedExtensions: recipeExtensions,
|
||||
availableExtensions: extensionOptions,
|
||||
recipeConfig,
|
||||
});
|
||||
|
||||
@@ -97,9 +134,9 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
description,
|
||||
instructions,
|
||||
activities,
|
||||
extensions: selectedExtensions
|
||||
extensions: recipeExtensions
|
||||
.map((name) => {
|
||||
const extension = availableExtensions.find((e) => e.name === name);
|
||||
const extension = extensionOptions.find((e) => e.name === name);
|
||||
console.log('Looking for extension:', name, 'Found:', extension);
|
||||
if (!extension) return null;
|
||||
|
||||
@@ -139,6 +176,8 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
const handleOpenAgent = () => {
|
||||
if (validateForm()) {
|
||||
const updatedConfig = getCurrentConfig();
|
||||
// Clear stored extensions when submitting
|
||||
localStorage.removeItem('recipe_editor_extensions');
|
||||
window.electron.createChatWindow(
|
||||
undefined,
|
||||
undefined,
|
||||
@@ -166,6 +205,13 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
});
|
||||
};
|
||||
|
||||
// Reset extensionsLoaded when section changes away from extensions
|
||||
useEffect(() => {
|
||||
if (activeSection !== 'extensions') {
|
||||
setExtensionsLoaded(false);
|
||||
}
|
||||
}, [activeSection]);
|
||||
|
||||
// Render expanded section content
|
||||
const renderSectionContent = () => {
|
||||
switch (activeSection) {
|
||||
@@ -258,49 +304,23 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
</div>
|
||||
<div className="mb-8 mt-6">
|
||||
<h2 className="text-2xl font-medium mb-2 text-textProminent">Extensions</h2>
|
||||
<p className="text-textSubtle">
|
||||
Choose which extensions will be available to your agent.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{availableExtensions.map((extension) => (
|
||||
<button
|
||||
key={extension.name}
|
||||
className="p-4 border border-borderSubtle rounded-lg flex justify-between items-center w-full text-left hover:bg-bgSubtle bg-bgApp"
|
||||
onClick={() => handleExtensionToggle(extension.name)}
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-medium text-textProminent">{extension.name}</h3>
|
||||
<p className="text-sm text-textSubtle">
|
||||
{extension.description || 'No description available'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative inline-block w-10 align-middle select-none">
|
||||
<div
|
||||
className={`w-10 h-6 rounded-full transition-colors duration-200 ease-in-out ${
|
||||
selectedExtensions.includes(extension.name)
|
||||
? 'bg-bgAppInverse'
|
||||
: 'bg-borderSubtle'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-6 h-6 rounded-full bg-bgApp border-2 transform transition-transform duration-200 ease-in-out ${
|
||||
selectedExtensions.includes(extension.name)
|
||||
? 'translate-x-4 border-bgAppInverse'
|
||||
: 'translate-x-0 border-borderSubtle'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
<p className="text-textSubtle">Select extensions to bundle in the recipe</p>
|
||||
</div>
|
||||
{extensionsLoaded ? (
|
||||
<ExtensionList
|
||||
extensions={extensionOptions}
|
||||
onToggle={handleExtensionToggle}
|
||||
isStatic={true}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-center py-8 text-textSubtle">Loading extensions...</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2 py-2">
|
||||
<div>
|
||||
<h2 className="text-lg font-medium mb-2 text-textProminent">Agent</h2>
|
||||
<input
|
||||
@@ -315,7 +335,7 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
className={`w-full p-3 border rounded-lg bg-bgApp text-textStandard ${
|
||||
errors.title ? 'border-red-500' : 'border-borderSubtle'
|
||||
}`}
|
||||
placeholder="Agent Name (required)"
|
||||
placeholder="Agent Recipe Name (required)"
|
||||
/>
|
||||
{errors.title && <div className="text-red-500 text-sm mt-1">{errors.title}</div>}
|
||||
</div>
|
||||
@@ -348,7 +368,7 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
<div className="text-left">
|
||||
<h3 className="font-medium text-textProminent">Activities</h3>
|
||||
<p className="text-textSubtle text-sm">
|
||||
Starting activities present in the home panel on a fresh goose session
|
||||
Starting activities present in the home panel on a fresh session
|
||||
</p>
|
||||
</div>
|
||||
<ChevronRight className="w-5 h-5 mt-1 text-iconSubtle" />
|
||||
@@ -360,9 +380,7 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
>
|
||||
<div className="text-left">
|
||||
<h3 className="font-medium text-textProminent">Instructions</h3>
|
||||
<p className="text-textSubtle text-sm">
|
||||
Starting activities present in the home panel on a fresh goose session
|
||||
</p>
|
||||
<p className="text-textSubtle text-sm">Recipe instructions sent to the model</p>
|
||||
</div>
|
||||
<ChevronRight className="w-5 h-5 mt-1 text-iconSubtle" />
|
||||
</button>
|
||||
@@ -374,7 +392,7 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
<div className="text-left">
|
||||
<h3 className="font-medium text-textProminent">Extensions</h3>
|
||||
<p className="text-textSubtle text-sm">
|
||||
Starting activities present in the home panel on a fresh goose session
|
||||
Extensions to be enabled by default with this recipe
|
||||
</p>
|
||||
</div>
|
||||
<ChevronRight className="w-5 h-5 mt-1 text-iconSubtle" />
|
||||
@@ -397,7 +415,7 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex flex-col space-y-2 pt-4">
|
||||
<div className="flex flex-col space-y-2 pt-1">
|
||||
<button
|
||||
onClick={handleOpenAgent}
|
||||
className="w-full p-3 bg-bgAppInverse text-textProminentInverse rounded-lg hover:bg-bgStandardInverse disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
@@ -406,7 +424,10 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
Open agent
|
||||
</button>
|
||||
<button
|
||||
onClick={() => window.close()}
|
||||
onClick={() => {
|
||||
localStorage.removeItem('recipe_editor_extensions');
|
||||
window.close();
|
||||
}}
|
||||
className="w-full p-3 text-textSubtle rounded-lg hover:bg-bgSubtle"
|
||||
>
|
||||
Cancel
|
||||
@@ -425,10 +446,11 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
<Geese className="w-12 h-12 text-iconProminent" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-medium text-center text-textProminent">
|
||||
Create custom agent
|
||||
Create an agent recipe
|
||||
</h1>
|
||||
<p className="text-textSubtle text-center mt-2 text-sm">
|
||||
Your custom agent can be shared with others
|
||||
Your custom agent recipe can be shared with others. Fill in the sections below to
|
||||
create!
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { ScrollArea } from '../ui/scroll-area';
|
||||
import BackButton from '../ui/BackButton';
|
||||
import type { View } from '../../App';
|
||||
|
||||
@@ -19,9 +19,22 @@ import { ExtensionConfig } from '../../../api/types.gen';
|
||||
interface ExtensionSectionProps {
|
||||
deepLinkConfig?: ExtensionConfig;
|
||||
showEnvVars?: boolean;
|
||||
hideButtons?: boolean;
|
||||
hideHeader?: boolean;
|
||||
disableConfiguration?: boolean;
|
||||
customToggle?: (extension: FixedExtensionEntry) => Promise<boolean | void>;
|
||||
selectedExtensions?: string[]; // Add controlled state
|
||||
}
|
||||
|
||||
export default function ExtensionsSection({ deepLinkConfig, showEnvVars }: ExtensionSectionProps) {
|
||||
export default function ExtensionsSection({
|
||||
deepLinkConfig,
|
||||
showEnvVars,
|
||||
hideButtons,
|
||||
hideHeader,
|
||||
disableConfiguration,
|
||||
customToggle,
|
||||
selectedExtensions = [],
|
||||
}: ExtensionSectionProps) {
|
||||
const { getExtensions, addExtension, removeExtension } = useConfig();
|
||||
const [extensions, setExtensions] = useState<FixedExtensionEntry[]>([]);
|
||||
const [selectedExtension, setSelectedExtension] = useState<FixedExtensionEntry | null>(null);
|
||||
@@ -37,22 +50,35 @@ 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) => {
|
||||
// First sort by builtin
|
||||
if (a.type === 'builtin' && b.type !== 'builtin') return -1;
|
||||
if (a.type !== 'builtin' && b.type === 'builtin') return 1;
|
||||
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;
|
||||
// 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);
|
||||
});
|
||||
// Finally sort alphabetically within each group
|
||||
return a.name.localeCompare(b.name);
|
||||
})
|
||||
.map((ext) => ({
|
||||
...ext,
|
||||
// Use selectedExtensions to determine enabled state in recipe editor
|
||||
enabled: disableConfiguration ? selectedExtensions.includes(ext.name) : ext.enabled,
|
||||
}));
|
||||
|
||||
console.log(
|
||||
'Setting extensions with selectedExtensions:',
|
||||
selectedExtensions,
|
||||
'Extensions:',
|
||||
sortedExtensions
|
||||
);
|
||||
setExtensions(sortedExtensions);
|
||||
}, [getExtensions]);
|
||||
}, [getExtensions, disableConfiguration, selectedExtensions]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchExtensions();
|
||||
@@ -60,6 +86,17 @@ export default function ExtensionsSection({ deepLinkConfig, showEnvVars }: Exten
|
||||
}, []);
|
||||
|
||||
const handleExtensionToggle = async (extension: FixedExtensionEntry) => {
|
||||
if (customToggle) {
|
||||
await customToggle(extension);
|
||||
// After custom toggle, update the local state to reflect the change
|
||||
setExtensions((prevExtensions) =>
|
||||
prevExtensions.map((ext) =>
|
||||
ext.name === extension.name ? { ...ext, enabled: !ext.enabled } : ext
|
||||
)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If extension is enabled, we are trying to toggle if off, otherwise on
|
||||
const toggleDirection = extension.enabled ? 'toggleOff' : 'toggleOn';
|
||||
const extensionConfig = extractExtensionConfig(extension);
|
||||
@@ -149,37 +186,46 @@ export default function ExtensionsSection({ deepLinkConfig, showEnvVars }: Exten
|
||||
|
||||
return (
|
||||
<section id="extensions" className="px-8">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h2 className="text-xl font-medium text-textStandard">Extensions</h2>
|
||||
</div>
|
||||
<div className="border-b border-borderSubtle pb-8">
|
||||
<p className="text-sm text-textStandard mb-6">
|
||||
These extensions use the Model Context Protocol (MCP). They can expand Goose's
|
||||
capabilities using three main components: Prompts, Resources, and Tools.
|
||||
</p>
|
||||
{!hideHeader && (
|
||||
<>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h2 className="text-xl font-medium text-textStandard">Extensions</h2>
|
||||
</div>
|
||||
<div className="border-b border-borderSubtle">
|
||||
<p className="text-sm text-textStandard mb-6">
|
||||
These extensions use the Model Context Protocol (MCP). They can expand Goose's
|
||||
capabilities using three main components: Prompts, Resources, and Tools.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className={`${!hideHeader ? '' : 'border-b border-borderSubtle'} pb-8`}>
|
||||
<ExtensionList
|
||||
extensions={extensions}
|
||||
onToggle={handleExtensionToggle}
|
||||
onConfigure={handleConfigureClick}
|
||||
disableConfiguration={disableConfiguration}
|
||||
/>
|
||||
|
||||
<div className="flex gap-4 pt-4 w-full">
|
||||
<Button
|
||||
className="flex items-center gap-2 justify-center text-white dark:text-black bg-bgAppInverse hover:bg-bgStandardInverse [&>svg]:!size-4"
|
||||
onClick={() => setIsAddModalOpen(true)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add custom extension
|
||||
</Button>
|
||||
<Button
|
||||
className="flex items-center gap-2 justify-center text-textStandard bg-bgApp border border-borderSubtle hover:border-borderProminent hover:bg-bgApp [&>svg]:!size-4"
|
||||
onClick={() => window.open('https://block.github.io/goose/v1/extensions/', '_blank')}
|
||||
>
|
||||
<GPSIcon size={12} />
|
||||
Browse extensions
|
||||
</Button>
|
||||
</div>
|
||||
{!hideButtons && (
|
||||
<div className="flex gap-4 pt-4 w-full">
|
||||
<Button
|
||||
className="flex items-center gap-2 justify-center text-white dark:text-black bg-bgAppInverse hover:bg-bgStandardInverse [&>svg]:!size-4"
|
||||
onClick={() => setIsAddModalOpen(true)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add custom extension
|
||||
</Button>
|
||||
<Button
|
||||
className="flex items-center gap-2 justify-center text-textStandard bg-bgApp border border-borderSubtle hover:border-borderProminent hover:bg-bgApp [&>svg]:!size-4"
|
||||
onClick={() => window.open('https://block.github.io/goose/v1/extensions/', '_blank')}
|
||||
>
|
||||
<GPSIcon size={12} />
|
||||
Browse extensions
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal for updating an existing extension */}
|
||||
{isModalOpen && selectedExtension && (
|
||||
|
||||
@@ -6,11 +6,17 @@ import { getSubtitle, getFriendlyTitle } from './ExtensionList';
|
||||
|
||||
interface ExtensionItemProps {
|
||||
extension: FixedExtensionEntry;
|
||||
onToggle: (extension: FixedExtensionEntry) => Promise<boolean | void>;
|
||||
onConfigure: (extension: FixedExtensionEntry) => void;
|
||||
onToggle: (extension: FixedExtensionEntry) => Promise<boolean | void> | void;
|
||||
onConfigure?: (extension: FixedExtensionEntry) => void;
|
||||
isStatic?: boolean; // to not allow users to edit configuration
|
||||
}
|
||||
|
||||
export default function ExtensionItem({ extension, onToggle, onConfigure }: ExtensionItemProps) {
|
||||
export default function ExtensionItem({
|
||||
extension,
|
||||
onToggle,
|
||||
onConfigure,
|
||||
isStatic,
|
||||
}: ExtensionItemProps) {
|
||||
// Add local state to track the visual toggle state
|
||||
const [visuallyEnabled, setVisuallyEnabled] = useState(extension.enabled);
|
||||
// Track if we're in the process of toggling
|
||||
@@ -59,7 +65,9 @@ export default function ExtensionItem({ extension, onToggle, onConfigure }: Exte
|
||||
|
||||
// Bundled extensions and builtins are not editable
|
||||
// Over time we can take the first part of the conditional away as people have bundled: true in their config.yaml entries
|
||||
const editable = !(extension.type === 'builtin' || extension.bundled);
|
||||
|
||||
// allow configuration editing if extension is not a builtin/bundled extension AND isStatic = false
|
||||
const editable = !(extension.type === 'builtin' || extension.bundled) && !isStatic;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -78,7 +86,7 @@ export default function ExtensionItem({ extension, onToggle, onConfigure }: Exte
|
||||
{editable && (
|
||||
<button
|
||||
className="text-textSubtle hover:text-textStandard"
|
||||
onClick={() => onConfigure(extension)}
|
||||
onClick={() => (onConfigure ? onConfigure(extension) : () => {})}
|
||||
>
|
||||
<Gear className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
@@ -7,11 +7,17 @@ import { combineCmdAndArgs, removeShims } from '../utils';
|
||||
|
||||
interface ExtensionListProps {
|
||||
extensions: FixedExtensionEntry[];
|
||||
onToggle: (extension: FixedExtensionEntry) => Promise<boolean | void>;
|
||||
onConfigure: (extension: FixedExtensionEntry) => void;
|
||||
onToggle: (extension: FixedExtensionEntry) => Promise<boolean | void> | void;
|
||||
onConfigure?: (extension: FixedExtensionEntry) => void;
|
||||
isStatic?: boolean;
|
||||
}
|
||||
|
||||
export default function ExtensionList({ extensions, onToggle, onConfigure }: ExtensionListProps) {
|
||||
export default function ExtensionList({
|
||||
extensions,
|
||||
onToggle,
|
||||
onConfigure,
|
||||
isStatic,
|
||||
}: ExtensionListProps) {
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-2 mb-2">
|
||||
{extensions.map((extension) => (
|
||||
@@ -20,6 +26,7 @@ export default function ExtensionList({ extensions, onToggle, onConfigure }: Ext
|
||||
extension={extension}
|
||||
onToggle={onToggle}
|
||||
onConfigure={onConfigure}
|
||||
isStatic={isStatic}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -150,7 +150,6 @@ export default function ProviderConfigurationModal() {
|
||||
|
||||
// go through the keys are remove them
|
||||
for (const param of params) {
|
||||
console.log('param', param.name, 'secret', param.secret);
|
||||
await remove(param.name, param.secret);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user