mirror of
https://github.com/aljazceru/goose.git
synced 2026-02-15 19:44:20 +01:00
ui: remove and update extensions (#1847)
This commit is contained in:
@@ -150,7 +150,7 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) {
|
||||
ToastError({
|
||||
title: provider,
|
||||
msg: `Failed to ${providers.find((p) => p.id === selectedId)?.isConfigured ? 'update' : 'add'} configuration`,
|
||||
errorMessage: error.message,
|
||||
traceback: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -80,7 +80,7 @@ export function ConfigureBuiltInExtensionModal({
|
||||
ToastError({
|
||||
title: extension.name,
|
||||
msg: `Failed to configure the extension`,
|
||||
errorMessage: error.message,
|
||||
traceback: error.message,
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
|
||||
@@ -82,7 +82,7 @@ export function ConfigureExtensionModal({
|
||||
ToastError({
|
||||
title: extension.name,
|
||||
msg: `Failed to configure extension`,
|
||||
errorMessage: error.message,
|
||||
traceback: error.message,
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
|
||||
@@ -99,7 +99,7 @@ export function ManualExtensionModal({ isOpen, onClose, onSubmit }: ManualExtens
|
||||
resetForm();
|
||||
} catch (error) {
|
||||
console.error('Error configuring extension:', error);
|
||||
ToastError({ title: 'Failed to configure extension', errorMessage: error.message });
|
||||
ToastError({ title: 'Failed to configure extension', traceback: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@ export function ToastSuccess({ title, msg, toastOptions = {} }: ToastSuccessProp
|
||||
type ToastErrorProps = {
|
||||
title?: string;
|
||||
msg?: string;
|
||||
errorMessage?: string;
|
||||
traceback?: string;
|
||||
toastOptions?: ToastOptions;
|
||||
};
|
||||
export function ToastError({ title, msg, errorMessage, toastOptions }: ToastErrorProps) {
|
||||
export function ToastError({ title, msg, traceback, toastOptions }: ToastErrorProps) {
|
||||
return toast.error(
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-grow">
|
||||
@@ -35,17 +35,17 @@ export function ToastError({ title, msg, errorMessage, toastOptions }: ToastErro
|
||||
{msg ? <div>{msg}</div> : null}
|
||||
</div>
|
||||
<div className="flex-none flex items-center">
|
||||
{errorMessage ? (
|
||||
{traceback ? (
|
||||
<button
|
||||
className="text-textProminentInverse font-medium"
|
||||
onClick={() => navigator.clipboard.writeText(errorMessage)}
|
||||
onClick={() => navigator.clipboard.writeText(traceback)}
|
||||
>
|
||||
Copy error
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>,
|
||||
{ ...commonToastOptions, autoClose: errorMessage ? false : 5000, ...toastOptions }
|
||||
{ ...commonToastOptions, autoClose: traceback ? false : 5000, ...toastOptions }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export function useHandleModelSelection() {
|
||||
ToastError({
|
||||
title: model.name,
|
||||
msg: `Failed to switch to model`,
|
||||
errorMessage: error.message,
|
||||
traceback: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -158,7 +158,7 @@ export function ConfigureProvidersGrid() {
|
||||
ToastError({
|
||||
title: provider,
|
||||
msg: `Failed to ${providers.find((p) => p.id === selectedForSetup)?.isConfigured ? 'update' : 'add'} configuration`,
|
||||
errorMessage: error.message,
|
||||
traceback: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -181,7 +181,7 @@ export function ConfigureProvidersGrid() {
|
||||
// Check if the selected provider is currently active
|
||||
if (currentModel?.provider === providerToDelete.name) {
|
||||
const msg = `Cannot delete the configuration because it's the provider of the current model (${currentModel.name}). Please switch to a different model first.`;
|
||||
ToastError({ title: providerToDelete.name, msg, errorMessage: msg });
|
||||
ToastError({ title: providerToDelete.name, msg, traceback: msg });
|
||||
setIsConfirmationOpen(false);
|
||||
return;
|
||||
}
|
||||
@@ -221,7 +221,7 @@ export function ConfigureProvidersGrid() {
|
||||
ToastError({
|
||||
title: providerToDelete.name,
|
||||
msg: 'Failed to delete configuration',
|
||||
errorMessage: error.message,
|
||||
traceback: error.message,
|
||||
});
|
||||
}
|
||||
setIsConfirmationOpen(false);
|
||||
|
||||
@@ -9,20 +9,20 @@ import {
|
||||
createExtensionConfig,
|
||||
ExtensionFormData,
|
||||
extensionToFormData,
|
||||
extractExtensionConfig,
|
||||
getDefaultFormData,
|
||||
} from './utils';
|
||||
import { useAgent } from '../../../agent/UpdateAgent';
|
||||
import { activateExtension } from '.';
|
||||
|
||||
import { activateExtension, deleteExtension, toggleExtension, updateExtension } from './index';
|
||||
|
||||
export default function ExtensionsSection() {
|
||||
const { toggleExtension, getExtensions, addExtension, removeExtension } = useConfig();
|
||||
const { getExtensions, addExtension, removeExtension } = useConfig();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [extensions, setExtensions] = useState<FixedExtensionEntry[]>([]);
|
||||
const [selectedExtension, setSelectedExtension] = useState<FixedExtensionEntry | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||
const { updateAgent, addExtensionToAgent } = useAgent();
|
||||
|
||||
const fetchExtensions = async () => {
|
||||
setLoading(true);
|
||||
@@ -44,13 +44,17 @@ export default function ExtensionsSection() {
|
||||
fetchExtensions();
|
||||
}, []);
|
||||
|
||||
const handleExtensionToggle = async (name: string) => {
|
||||
try {
|
||||
await toggleExtension(name);
|
||||
fetchExtensions(); // Refresh the list after toggling
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle extension:', error);
|
||||
}
|
||||
const handleExtensionToggle = async (extension: FixedExtensionEntry) => {
|
||||
// If extension is enabled, we are trying to toggle if off, otherwise on
|
||||
const toggleDirection = extension.enabled ? 'toggleOff' : 'toggleOn';
|
||||
const extensionConfig = extractExtensionConfig(extension);
|
||||
await toggleExtension({
|
||||
toggle: toggleDirection,
|
||||
extensionConfig: extensionConfig,
|
||||
addToConfig: addExtension,
|
||||
removeFromConfig: removeExtension,
|
||||
});
|
||||
await fetchExtensions(); // Refresh the list after toggling
|
||||
};
|
||||
|
||||
const handleConfigureClick = (extension: FixedExtensionEntry) => {
|
||||
@@ -60,38 +64,29 @@ export default function ExtensionsSection() {
|
||||
|
||||
const handleAddExtension = async (formData: ExtensionFormData) => {
|
||||
const extensionConfig = createExtensionConfig(formData);
|
||||
|
||||
try {
|
||||
await activateExtension(formData.name, extensionConfig, addExtension);
|
||||
console.log('attempting to add extension');
|
||||
await updateAgent(extensionConfig);
|
||||
handleModalClose();
|
||||
await fetchExtensions(); // Refresh the list after adding
|
||||
} catch (error) {
|
||||
console.error('Failed to add extension:', error);
|
||||
}
|
||||
// TODO: replace activateExtension in index
|
||||
// TODO: make sure error handling works
|
||||
await activateExtension({ addToConfig: addExtension, extensionConfig: extensionConfig });
|
||||
handleModalClose();
|
||||
await fetchExtensions();
|
||||
};
|
||||
|
||||
const handleUpdateExtension = async (formData: ExtensionFormData) => {
|
||||
const extensionConfig = createExtensionConfig(formData);
|
||||
|
||||
try {
|
||||
await activateExtension(formData.name, extensionConfig, addExtension);
|
||||
handleModalClose();
|
||||
fetchExtensions(); // Refresh the list after updating
|
||||
} catch (error) {
|
||||
console.error('Failed to update extension configuration:', error);
|
||||
}
|
||||
await updateExtension({
|
||||
enabled: formData.enabled,
|
||||
extensionConfig: extensionConfig,
|
||||
addToConfig: addExtension,
|
||||
});
|
||||
handleModalClose();
|
||||
await fetchExtensions();
|
||||
};
|
||||
|
||||
const handleDeleteExtension = async (name: string) => {
|
||||
try {
|
||||
await removeExtension(name);
|
||||
handleModalClose();
|
||||
fetchExtensions(); // Refresh the list after deleting
|
||||
} catch (error) {
|
||||
console.error('Failed to delete extension:', error);
|
||||
}
|
||||
await deleteExtension({ name, removeFromConfig: removeExtension });
|
||||
handleModalClose();
|
||||
await fetchExtensions();
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { toast } from 'react-toastify';
|
||||
import { ToastError, ToastLoading, ToastSuccess } from '../../settings/models/toasts';
|
||||
|
||||
// Default extension timeout in seconds
|
||||
// TODO: keep in sync with rust better
|
||||
export const DEFAULT_EXTENSION_TIMEOUT = 300;
|
||||
|
||||
// Type definition for built-in extensions from JSON
|
||||
@@ -33,7 +34,7 @@ function handleError(message: string, shouldThrow = false): void {
|
||||
ToastError({
|
||||
title: 'Error',
|
||||
msg: message,
|
||||
errorMessage: message,
|
||||
traceback: message,
|
||||
});
|
||||
console.error(message);
|
||||
if (shouldThrow) {
|
||||
@@ -57,6 +58,11 @@ async function replaceWithShims(cmd: string) {
|
||||
return cmd;
|
||||
}
|
||||
|
||||
interface activateExtensionProps {
|
||||
addToConfig: (name: string, extensionConfig: ExtensionConfig, enabled: boolean) => Promise<void>;
|
||||
extensionConfig: ExtensionConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates an extension by adding it to both the config system and the API.
|
||||
* @param name The extension name
|
||||
@@ -64,67 +70,151 @@ async function replaceWithShims(cmd: string) {
|
||||
* @param addExtensionFn Function to add extension to config
|
||||
* @returns Promise that resolves when activation is complete
|
||||
*/
|
||||
export async function activateExtension(
|
||||
name: string,
|
||||
config: ExtensionConfig,
|
||||
addExtensionFn: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>
|
||||
): Promise<void> {
|
||||
let toastId;
|
||||
export async function activateExtension({
|
||||
addToConfig,
|
||||
extensionConfig,
|
||||
}: activateExtensionProps): Promise<void> {
|
||||
try {
|
||||
// Show loading toast
|
||||
toastId = ToastLoading({ title: name, msg: 'Adding extension...' });
|
||||
|
||||
// First add to the config system
|
||||
await addExtensionFn(nameToKey(name), config, true);
|
||||
|
||||
// Then call the API endpoint
|
||||
const response = await fetch(getApiUrl('/extensions/add'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Secret-Key': getSecretKey(),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: config.type,
|
||||
name: nameToKey(name),
|
||||
cmd: await replaceWithShims(config.cmd),
|
||||
args: config.args || [],
|
||||
env_keys: config.envs ? Object.keys(config.envs) : undefined,
|
||||
timeout: config.timeout,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.error) {
|
||||
if (toastId) toast.dismiss(toastId);
|
||||
ToastSuccess({ title: name, msg: 'Successfully enabled extension' });
|
||||
} else {
|
||||
const errorMessage = `Error adding extension`;
|
||||
console.error(errorMessage);
|
||||
if (toastId) toast.dismiss(toastId);
|
||||
ToastError({
|
||||
title: name,
|
||||
msg: errorMessage,
|
||||
errorMessage: data.message,
|
||||
});
|
||||
}
|
||||
// AddToAgent
|
||||
await AddToAgent(extensionConfig);
|
||||
} catch (error) {
|
||||
const errorMessage = `Failed to add ${name} extension: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
||||
console.error(errorMessage);
|
||||
if (toastId) toast.dismiss(toastId);
|
||||
ToastError({
|
||||
title: name,
|
||||
msg: 'Failed to add extension',
|
||||
errorMessage: error.message,
|
||||
});
|
||||
// add to config with enabled = false
|
||||
await addToConfig(extensionConfig.name, extensionConfig, false);
|
||||
// show user the error, return
|
||||
console.log('error', error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Then add to config
|
||||
try {
|
||||
await addToConfig(extensionConfig.name, extensionConfig, true);
|
||||
} catch (error) {
|
||||
// remove from Agent
|
||||
await RemoveFromAgent(extensionConfig.name);
|
||||
// config error workflow
|
||||
console.log('error', error);
|
||||
}
|
||||
}
|
||||
|
||||
interface updateExtensionProps {
|
||||
enabled: boolean;
|
||||
addToConfig: (name: string, extensionConfig: ExtensionConfig, enabled: boolean) => Promise<void>;
|
||||
extensionConfig: ExtensionConfig;
|
||||
}
|
||||
|
||||
// updating -- no change to enabled state
|
||||
export async function updateExtension({
|
||||
enabled,
|
||||
addToConfig,
|
||||
extensionConfig,
|
||||
}: updateExtensionProps) {
|
||||
if (enabled) {
|
||||
try {
|
||||
// AddToAgent
|
||||
await AddToAgent(extensionConfig);
|
||||
} catch (error) {
|
||||
// i think only error that gets thrown here is when it's not from the response... rest are handled by agent
|
||||
console.log('error', error);
|
||||
// failed to add to agent -- show that error to user and do not update the config file
|
||||
return;
|
||||
}
|
||||
|
||||
// Then add to config
|
||||
try {
|
||||
await addToConfig(extensionConfig.name, extensionConfig, enabled);
|
||||
} catch (error) {
|
||||
// config error workflow
|
||||
console.log('error', error);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await addToConfig(extensionConfig.name, extensionConfig, enabled);
|
||||
} catch (error) {
|
||||
// TODO: Add to agent with previous configuration and raise error
|
||||
// for now just log error
|
||||
console.log('error', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface toggleExtensionProps {
|
||||
toggle: 'toggleOn' | 'toggleOff';
|
||||
extensionConfig: ExtensionConfig;
|
||||
addToConfig: (name: string, extensionConfig: ExtensionConfig, enabled: boolean) => Promise<void>;
|
||||
removeFromConfig: (name: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export async function toggleExtension({
|
||||
toggle,
|
||||
extensionConfig,
|
||||
addToConfig,
|
||||
}: toggleExtensionProps) {
|
||||
// disabled to enabled
|
||||
if (toggle == 'toggleOn') {
|
||||
try {
|
||||
// add to agent
|
||||
await AddToAgent(extensionConfig);
|
||||
} catch (error) {
|
||||
// do nothing raise error
|
||||
// show user error
|
||||
console.log('Error adding extension to agent. Error:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
// update the config
|
||||
try {
|
||||
await addToConfig(extensionConfig.name, extensionConfig, true);
|
||||
} catch (error) {
|
||||
// remove from agent?
|
||||
await RemoveFromAgent(extensionConfig.name);
|
||||
}
|
||||
} else if (toggle == 'toggleOff') {
|
||||
// enabled to disabled
|
||||
try {
|
||||
await RemoveFromAgent(extensionConfig.name);
|
||||
} catch (error) {
|
||||
// note there was an error, but remove from config anyway
|
||||
console.error('Error removing extension from agent', extensionConfig.name, error);
|
||||
}
|
||||
// update the config
|
||||
try {
|
||||
await addToConfig(extensionConfig.name, extensionConfig, false);
|
||||
} catch (error) {
|
||||
// TODO: Add to agent with previous configuration
|
||||
console.log('Error removing extension from config', extensionConfig.name, 'Error:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface deleteExtensionProps {
|
||||
name: string;
|
||||
removeFromConfig: (name: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export async function deleteExtension({ name, removeFromConfig }: deleteExtensionProps) {
|
||||
// remove from agent
|
||||
await RemoveFromAgent(name);
|
||||
|
||||
try {
|
||||
await removeFromConfig(name);
|
||||
} catch (error) {
|
||||
console.log('Failed to remove extension from config after removing from agent. Error:', error);
|
||||
// TODO: tell user to restart goose and try again to remove (will still be present in settings but not on agent until restart)
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
/*Deeplinks*/
|
||||
}
|
||||
|
||||
export async function addExtensionFromDeepLink(
|
||||
url: string,
|
||||
addExtensionFn: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>,
|
||||
addExtensionFn: (
|
||||
name: string,
|
||||
extensionConfig: ExtensionConfig,
|
||||
enabled: boolean
|
||||
) => Promise<void>,
|
||||
setView: (view: string, options: { extensionId: string; showEnvVars: boolean }) => void
|
||||
) {
|
||||
const parsedUrl = new URL(url);
|
||||
@@ -202,7 +292,11 @@ export async function addExtensionFromDeepLink(
|
||||
}
|
||||
|
||||
// If no env vars are required, proceed with adding the extension
|
||||
await activateExtension(name, config, addExtensionFn);
|
||||
await activateExtension({ extensionConfig: config, addToConfig: addExtensionFn });
|
||||
}
|
||||
|
||||
{
|
||||
/*Built ins*/
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,3 +366,109 @@ export async function initializeBuiltInExtensions(
|
||||
// Call with an empty list to ensure all built-ins are added
|
||||
await syncBuiltInExtensions([], addExtensionFn);
|
||||
}
|
||||
|
||||
{
|
||||
/* Agent-related helper functions */
|
||||
}
|
||||
async function extensionApiCall<T>(
|
||||
endpoint: string,
|
||||
payload: any,
|
||||
actionType: 'adding' | 'removing',
|
||||
extensionName: string
|
||||
): Promise<Response> {
|
||||
let toastId;
|
||||
const actionVerb = actionType === 'adding' ? 'Adding' : 'Removing';
|
||||
const pastVerb = actionType === 'adding' ? 'added' : 'removed';
|
||||
|
||||
try {
|
||||
if (actionType === 'adding') {
|
||||
// Show loading toast
|
||||
toastId = ToastLoading({
|
||||
title: extensionName,
|
||||
msg: `${actionVerb} ${extensionName} extension...`,
|
||||
});
|
||||
// FIXME: this also shows when toggling -- should only show when you have modal up (fix: diff message for toggling)
|
||||
toast.info(
|
||||
'Press the ESC key on your keyboard to continue using goose while extension loads'
|
||||
);
|
||||
}
|
||||
|
||||
const response = await fetch(getApiUrl(endpoint), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Secret-Key': getSecretKey(),
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
// Handle non-OK responses
|
||||
if (!response.ok) {
|
||||
const errorMsg = `Server returned ${response.status}: ${response.statusText}`;
|
||||
console.error(errorMsg);
|
||||
|
||||
// Special handling for 428 Precondition Required (agent not initialized)
|
||||
if (response.status === 428 && actionType === 'adding') {
|
||||
if (toastId) toast.dismiss(toastId);
|
||||
ToastError({
|
||||
title: extensionName,
|
||||
msg: 'Agent is not initialized. Please initialize the agent first.',
|
||||
traceback: errorMsg,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
const msg = `Failed to ${actionType === 'adding' ? 'add' : 'remove'} ${extensionName} extension: ${errorMsg}`;
|
||||
console.error(msg);
|
||||
|
||||
if (toastId) toast.dismiss(toastId);
|
||||
ToastError({
|
||||
title: extensionName,
|
||||
msg: msg,
|
||||
traceback: errorMsg,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
// Parse response JSON safely
|
||||
let data;
|
||||
try {
|
||||
const text = await response.text();
|
||||
data = text ? JSON.parse(text) : { error: false };
|
||||
} catch (error) {
|
||||
console.warn('Could not parse response as JSON, assuming success', error);
|
||||
data = { error: false };
|
||||
}
|
||||
|
||||
if (!data.error) {
|
||||
if (toastId) toast.dismiss(toastId);
|
||||
ToastSuccess({ title: extensionName, msg: 'Successfully enabled extension' });
|
||||
} else {
|
||||
const errorMessage = `Error adding extension -- parsing data`;
|
||||
console.error(errorMessage);
|
||||
if (toastId) toast.dismiss(toastId);
|
||||
ToastError({
|
||||
title: extensionName,
|
||||
msg: errorMessage,
|
||||
traceback: data.message, // why data.message not data.error?
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
// Public functions
|
||||
export async function AddToAgent(extension: ExtensionConfig): Promise<Response> {
|
||||
if (extension.type === 'stdio') {
|
||||
console.log('extension command', extension.cmd);
|
||||
extension.cmd = await replaceWithShims(extension.cmd);
|
||||
console.log('next ext command', extension.cmd);
|
||||
}
|
||||
|
||||
return extensionApiCall('/extensions/add', extension, 'adding', extension.name);
|
||||
}
|
||||
|
||||
export async function RemoveFromAgent(name: string): Promise<Response> {
|
||||
return extensionApiCall('/extensions/remove', name, 'removing', name);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getSubtitle, getFriendlyTitle } from './ExtensionList';
|
||||
|
||||
interface ExtensionItemProps {
|
||||
extension: FixedExtensionEntry;
|
||||
onToggle: (name: string) => void;
|
||||
onToggle: (extension: FixedExtensionEntry) => void;
|
||||
onConfigure: (extension: FixedExtensionEntry) => void;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function ExtensionItem({ extension, onToggle, onConfigure }: Exte
|
||||
)}
|
||||
<Switch
|
||||
checked={extension.enabled}
|
||||
onCheckedChange={() => onToggle(extension.name)}
|
||||
onCheckedChange={() => onToggle(extension)}
|
||||
variant="mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { combineCmdAndArgs } from '../utils';
|
||||
|
||||
interface ExtensionListProps {
|
||||
extensions: FixedExtensionEntry[];
|
||||
onToggle: (name: string) => void;
|
||||
onToggle: (extension: FixedExtensionEntry) => void;
|
||||
onConfigure: (extension: FixedExtensionEntry) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,3 +95,13 @@ export function splitCmdAndArgs(str: string): { cmd: string; args: string[] } {
|
||||
export function combineCmdAndArgs(cmd: string, args: string[]): string {
|
||||
return [cmd, ...args].join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the ExtensionConfig from a FixedExtensionEntry object
|
||||
* @param fixedEntry - The FixedExtensionEntry object
|
||||
* @returns The ExtensionConfig portion of the object
|
||||
*/
|
||||
export function extractExtensionConfig(fixedEntry: FixedExtensionEntry): ExtensionConfig {
|
||||
const { enabled, ...extensionConfig } = fixedEntry;
|
||||
return extensionConfig;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const AddModelModal = ({ onClose }: AddModelModalProps) => {
|
||||
} catch (e) {
|
||||
ToastError({
|
||||
title: 'Failed to add model',
|
||||
errorMessage: e.message,
|
||||
traceback: e.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user