feat: hook extensions up in settings-v2 (#1447)
This commit is contained in:
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user