feat: hook extensions up in settings-v2 (#1447)

This commit is contained in:
Alex Hancock
2025-02-28 21:27:55 -05:00
committed by GitHub
parent 7439b8199e
commit 92e39acece
13 changed files with 236 additions and 100 deletions
+53 -11
View File
@@ -1,5 +1,23 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import { Config } from '../api/config';
import {
readAllConfig,
readConfig,
removeConfig,
upsertConfig,
addExtension as apiAddExtension,
removeExtension as apiRemoveExtension,
updateExtension as apiUpdateExtension,
} from '../api';
import { client } from '../api/client.gen';
// Initialize client configuration
client.setConfig({
baseUrl: window.appConfig.get('GOOSE_API_HOST') + ':' + window.appConfig.get('GOOSE_PORT'),
headers: {
'Content-Type': 'application/json',
'X-Secret-Key': window.appConfig.get('secretKey'),
},
});
interface ConfigContextType {
config: Record<string, any>;
@@ -7,6 +25,7 @@ interface ConfigContextType {
read: (key: string) => Promise<any>;
remove: (key: string) => Promise<void>;
addExtension: (name: string, config: any) => Promise<void>;
updateExtension: (name: string, config: any) => Promise<void>;
removeExtension: (name: string) => Promise<void>;
}
@@ -22,42 +41,65 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
useEffect(() => {
// Load all configuration data on mount
(async () => {
const initialConfig = await Config.readAll();
setConfig(initialConfig || {});
const response = await readAllConfig();
setConfig(response.data.config || {});
})();
}, []);
const reloadConfig = async () => {
const newConfig = await Config.readAll();
setConfig(newConfig || {});
const response = await readAllConfig();
setConfig(response.data.config || {});
};
const upsert = async (key: string, value: any, isSecret?: boolean) => {
await Config.upsert(key, value, isSecret);
await upsertConfig({
body: {
key,
value,
is_secret: isSecret,
},
});
await reloadConfig();
};
const read = async (key: string) => {
return Config.read(key);
return await readConfig({
body: { key },
});
};
const remove = async (key: string) => {
await Config.remove(key);
await removeConfig({
body: { key },
});
await reloadConfig();
};
const addExtension = async (name: string, config: any) => {
await Config.addExtension(name, config);
await apiAddExtension({
body: { name, config },
});
await reloadConfig();
};
const removeExtension = async (name: string) => {
await Config.removeExtension(name);
await apiRemoveExtension({
body: { key: name },
});
await reloadConfig();
};
const updateExtension = async (name: string, config: any) => {
await apiUpdateExtension({
body: { name, config },
});
await reloadConfig();
};
return (
<ConfigContext.Provider value={{ config, upsert, read, remove, addExtension, removeExtension }}>
<ConfigContext.Provider
value={{ config, upsert, read, remove, addExtension, updateExtension, removeExtension }}
>
{children}
</ConfigContext.Provider>
);
@@ -1,45 +1,83 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Button } from '../ui/button';
import { Switch } from '../ui/switch';
import { Plus } from 'lucide-react';
import { Gear } from '../icons/Gear';
import { GPSIcon } from '../ui/icons';
import { useConfig } from '../ConfigContext';
interface ExtensionConfig {
args?: string[];
cmd?: string;
enabled: boolean;
envs?: Record<string, string>;
name: string;
type: 'stdio' | 'builtin';
}
interface ExtensionItem {
id: string;
title: string;
subtitle: string;
enabled: boolean;
canConfigure?: boolean;
canConfigure: boolean;
config: ExtensionConfig;
}
const extensionItems: ExtensionItem[] = [
{
id: 'dev',
title: 'Developer Tools',
subtitle: 'Code editing and shell access',
enabled: true,
canConfigure: true,
},
{
id: 'browser',
title: 'Web Browser',
subtitle: 'Internet access and web automation',
enabled: false,
canConfigure: true,
},
];
// Helper function to get a friendly title from extension name
const getFriendlyTitle = (name: string): string => {
return name
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
};
// Helper function to get a subtitle based on extension type and configuration
const getSubtitle = (config: ExtensionConfig): string => {
if (config.type === 'builtin') {
return 'Built-in extension';
}
return `${config.type.toUpperCase()} extension${config.cmd ? ` (${config.cmd})` : ''}`;
};
export default function ExtensionsSection() {
const [extensions, setExtensions] = useState<ExtensionItem[]>(extensionItems);
const { config, updateExtension } = useConfig();
const [extensions, setExtensions] = useState<ExtensionItem[]>([]);
const handleExtensionToggle = (id: string) => {
setExtensions(
extensions.map((extension) => ({
...extension,
enabled: extension.id === id ? !extension.enabled : extension.enabled,
}))
);
useEffect(() => {
if (config.extensions) {
const extensionItems: ExtensionItem[] = Object.entries(config.extensions).map(
([name, ext]) => {
const extensionConfig = ext as ExtensionConfig;
return {
id: name,
title: getFriendlyTitle(name),
subtitle: getSubtitle(extensionConfig),
enabled: extensionConfig.enabled,
canConfigure: extensionConfig.type === 'stdio' && !!extensionConfig.envs,
config: extensionConfig,
};
}
);
setExtensions(extensionItems);
}
}, [config.extensions]);
const handleExtensionToggle = async (id: string) => {
const extension = extensions.find((ext) => ext.id === id);
if (extension) {
const updatedConfig = {
...extension.config,
enabled: !extension.config.enabled,
};
try {
await updateExtension(id, updatedConfig);
} catch (error) {
console.error('Failed to update extension:', error);
// Here you might want to add a toast notification for error feedback
}
}
};
return (