add copy button and make extension builtins any time (#1961)

This commit is contained in:
Lily Delalande
2025-03-31 21:45:10 -04:00
committed by GitHub
parent c629823207
commit e7ad230957
11 changed files with 73 additions and 48 deletions

View File

@@ -63,7 +63,7 @@ export default function App() {
view: 'welcome',
viewOptions: {},
});
const { getExtensions, addExtension, read } = useConfig();
const { getExtensions, addExtension, read, upsert } = useConfig();
const initAttemptedRef = useRef(false);
// Utility function to extract the command from the link
@@ -91,6 +91,7 @@ export default function App() {
const initializeApp = async () => {
try {
const config = window.electron.getConfig();
const provider = config.GOOSE_PROVIDER ?? (await read('GOOSE_PROVIDER', false));
const model = config.GOOSE_MODEL ?? (await read('GOOSE_MODEL', false));

View File

@@ -9,6 +9,7 @@ import { ChatSmart, Idea, More, Refresh, Time, Send } from '../icons';
import { FolderOpen, Moon, Sliders, Sun } from 'lucide-react';
import { View } from '../../App';
import { useConfig } from '../ConfigContext';
import { toastService } from '../../toasts';
interface VersionInfo {
current_version: string;
@@ -262,7 +263,7 @@ export default function MoreMenu({
await remove('GOOSE_PROVIDER', false);
await remove('GOOSE_MODEL', false);
setOpen(false);
window.electron.createChatWindow();
setView('welcome');
}}
danger
subtitle="Clear selected model and restart (alpha)"

View File

@@ -76,7 +76,7 @@ export async function extensionApiCall(
} catch (error) {
// Final catch-all error handler
toastService.dismiss(toastId);
const msg = error.length < 100 ? error : `Failed to ${action.presentTense} extension`;
const msg = error.length < 70 ? error : `Failed to ${action.presentTense} extension`;
toastService.error({
title: extensionName,
msg: msg,

View File

@@ -1,18 +1,22 @@
import type { ExtensionConfig } from '../../../api/types.gen';
import { FixedExtensionEntry } from '../../ConfigContext';
import builtInExtensionsData from './built-in-extensions.json';
import bundledExtensionsData from './bundled-extensions.json';
import { nameToKey } from './utils';
// Type definition for built-in extensions from JSON
type BuiltinExtension = {
type BundledExtension = {
id: string;
name: string;
display_name: string;
description: string;
display_name?: string;
description?: string;
enabled: boolean;
type: 'builtin';
type: 'builtin' | 'stdio' | 'sse';
cmd?: string;
args?: string[];
uri?: string;
envs?: { [key: string]: string };
timeout?: number;
allow_configure?: boolean;
};
/**
@@ -24,43 +28,61 @@ type BuiltinExtension = {
* @param addExtensionFn Function to add a new extension to the config
* @returns Promise that resolves when sync is complete
*/
export async function syncBuiltInExtensions(
export async function syncBundledExtensions(
existingExtensions: FixedExtensionEntry[],
addExtensionFn: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>
): Promise<void> {
try {
console.log('Setting up built-in extensions... in syncBuiltinExtensions');
// Create a set of existing extension IDs for quick lookup
const existingExtensionKeys = new Set(existingExtensions.map((ext) => nameToKey(ext.name)));
console.log('existing extension ids', existingExtensionKeys);
// Cast the imported JSON data to the expected type
const builtinExtensions = builtInExtensionsData as BuiltinExtension[];
const bundledExtensions = bundledExtensionsData as BundledExtension[];
// Track how many extensions were added
let addedCount = 0;
// Check each built-in extension
for (const builtinExt of builtinExtensions) {
for (const bundledExt of bundledExtensions) {
// Only add if the extension doesn't already exist -- use the id
if (!existingExtensionKeys.has(builtinExt.id)) {
console.log(`Adding built-in extension: ${builtinExt.id}`);
// Convert to the ExtensionConfig format
const extConfig: ExtensionConfig = {
name: builtinExt.name,
display_name: builtinExt.display_name,
type: 'builtin',
timeout: builtinExt.timeout ?? 300,
if (!existingExtensionKeys.has(bundledExt.id)) {
console.log(`Adding built-in extension: ${bundledExt.id}`);
let extConfig: ExtensionConfig;
switch (bundledExt.type) {
case 'builtin':
extConfig = {
name: bundledExt.name,
display_name: bundledExt.display_name,
type: bundledExt.type,
timeout: bundledExt.timeout ?? 300,
};
break;
case 'stdio':
extConfig = {
name: bundledExt.name,
description: bundledExt.description,
type: bundledExt.type,
timeout: bundledExt.timeout,
cmd: bundledExt.cmd,
args: bundledExt.args,
envs: bundledExt.envs,
};
break;
case 'sse':
extConfig = {
name: bundledExt.name,
description: bundledExt.description,
type: bundledExt.type,
timeout: bundledExt.timeout,
uri: bundledExt.uri,
};
}
// Add the extension with its default enabled state
try {
await addExtensionFn(nameToKey(builtinExt.name), extConfig, builtinExt.enabled);
await addExtensionFn(bundledExt.name, extConfig, bundledExt.enabled);
addedCount++;
} catch (error) {
console.error(`Failed to add built-in extension ${builtinExt.name}:`, error);
console.error(`Failed to add built-in extension ${bundledExt.name}:`, error);
// Continue with other extensions even if one fails
}
}
@@ -81,9 +103,9 @@ export async function syncBuiltInExtensions(
* Function to initialize all built-in extensions for a first-time user.
* This can be called when the application is first installed.
*/
export async function initializeBuiltInExtensions(
export async function initializeBundledExtensions(
addExtensionFn: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>
): Promise<void> {
// Call with an empty list to ensure all built-ins are added
await syncBuiltInExtensions([], addExtensionFn);
await syncBundledExtensions([], addExtensionFn);
}

View File

@@ -11,7 +11,7 @@ export {
} from './extension-manager';
// Export built-in extension functions
export { syncBuiltInExtensions, initializeBuiltInExtensions } from './built-in';
export { syncBundledExtensions, initializeBundledExtensions } from './bundled-extensions';
// Export deeplink handling
export { addExtensionFromDeepLink } from './deeplink';

View File

@@ -4,13 +4,12 @@ import ModelSettingsButtons from './subcomponents/ModelSettingsButtons';
import { useConfig } from '../../ConfigContext';
import { toastError } from '../../../toasts';
import { UNKNOWN_PROVIDER_MSG, UNKNOWN_PROVIDER_TITLE } from './index';
interface ModelsSectionProps {
setView: (view: View) => void;
}
const UNKNOWN_PROVIDER_TITLE = 'Provider name error';
const UNKNOWN_PROVIDER_MSG = 'Unknown provider in config -- please inspect your config.yaml';
export default function ModelsSection({ setView }: ModelsSectionProps) {
const [provider, setProvider] = useState<string | null>(null);
const [model, setModel] = useState<string>('');

View File

@@ -8,13 +8,13 @@ import type { ExtensionConfig, FixedExtensionEntry } from '../../ConfigContext';
// titles
const CHANGE_MODEL_TOAST_TITLE = 'Model selected';
const START_AGENT_TITLE = 'Initialize agent';
const UNKNOWN_PROVIDER_TITLE = 'Provider name lookup';
export const UNKNOWN_PROVIDER_TITLE = 'Provider name lookup';
// errors
const SWITCH_MODEL_AGENT_ERROR_MSG = 'Failed to start agent with selected model';
const CONFIG_UPDATE_ERROR_MSG = 'Failed to update configuration settings';
const CONFIG_READ_MODEL_ERROR_MSG = 'Failed to read GOOSE_MODEL or GOOSE_PROVIDER from config';
const UNKNOWN_PROVIDER_MSG = 'Unknown provider in config -- please inspect your config.yaml';
export const UNKNOWN_PROVIDER_MSG = 'Unknown provider in config -- please inspect your config.yaml';
// success
const SWITCH_MODEL_SUCCESS_MSG = 'Successfully switched models';
@@ -114,11 +114,6 @@ export async function getCurrentModelAndProviderForDisplay({
try {
metadata = await getProviderMetadata(gooseProvider, getProviders);
} catch (error) {
toastError({
title: UNKNOWN_PROVIDER_TITLE,
msg: UNKNOWN_PROVIDER_MSG,
traceback: error,
});
return { model: gooseModel, provider: gooseProvider };
}
const providerDisplayName = metadata.display_name;

View File

@@ -6,6 +6,8 @@ import { useConfig } from '../../ConfigContext';
import { ProviderDetails } from '../../../api/types.gen';
import { initializeSystem } from '../../../utils/providerUtils';
import WelcomeGooseLogo from '../../WelcomeGooseLogo';
import { toastService } from '../../../toasts';
import { toast } from 'react-toastify';
interface ProviderSettingsProps {
onClose: () => void;
@@ -56,7 +58,6 @@ export default function ProviderSettings({ onClose, isOnboarding }: ProviderSett
const provider_name = provider.name;
const model = provider.metadata.default_model;
console.log(`Launching with provider: ${provider.name}`);
try {
// update the config
// set GOOSE_PROVIDER in the config file
@@ -76,6 +77,11 @@ export default function ProviderSettings({ onClose, isOnboarding }: ProviderSett
} catch (error) {
console.error(`Failed to initialize with provider ${provider_name}:`, error);
}
toastService.configure({ silent: false });
toastService.success({
title: 'Success!',
msg: `Started goose with ${model} by ${provider.metadata.display_name}. You can change the model via the lower right corner.`,
});
onClose();
},
[onClose, upsert]

View File

@@ -1,5 +1,6 @@
import { toast, ToastOptions } from 'react-toastify';
import React from 'react';
import { Button } from './components/ui/button';
export interface ToastServiceOptions {
silent?: boolean;
@@ -123,12 +124,12 @@ export function toastError({ title, msg, traceback, toastOptions }: ToastErrorPr
</div>
<div className="flex-none flex items-center">
{traceback ? (
<button
className="text-textProminentInverse font-medium"
<Button
className="text-textProminentInverse font-medium rt-variant-outline dark:bg-gray-300 bg-slate"
onClick={() => navigator.clipboard.writeText(traceback)}
>
Copy error
</button>
</Button>
) : null}
</div>
</div>,

View File

@@ -5,8 +5,8 @@ import { Model } from '../components/settings/models/ModelContext';
import { gooseModels } from '../components/settings/models/GooseModels';
import { initializeAgent } from '../agent';
import {
initializeBuiltInExtensions,
syncBuiltInExtensions,
initializeBundledExtensions,
syncBundledExtensions,
addToAgentOnStartup,
} from '../components/settings_v2/extensions';
import { extractExtensionConfig } from '../components/settings_v2/extensions/utils';
@@ -128,10 +128,10 @@ export const initializeSystem = async (
let refreshedExtensions = await options.getExtensions(false);
if (refreshedExtensions.length === 0) {
await initializeBuiltInExtensions(options.addExtension);
await initializeBundledExtensions(options.addExtension);
refreshedExtensions = await options.getExtensions(false);
} else {
await syncBuiltInExtensions(refreshedExtensions, options.addExtension);
await syncBundledExtensions(refreshedExtensions, options.addExtension);
}
// Add enabled extensions to agent