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', view: 'welcome',
viewOptions: {}, viewOptions: {},
}); });
const { getExtensions, addExtension, read } = useConfig(); const { getExtensions, addExtension, read, upsert } = useConfig();
const initAttemptedRef = useRef(false); const initAttemptedRef = useRef(false);
// Utility function to extract the command from the link // Utility function to extract the command from the link
@@ -91,6 +91,7 @@ export default function App() {
const initializeApp = async () => { const initializeApp = async () => {
try { try {
const config = window.electron.getConfig(); const config = window.electron.getConfig();
const provider = config.GOOSE_PROVIDER ?? (await read('GOOSE_PROVIDER', false)); const provider = config.GOOSE_PROVIDER ?? (await read('GOOSE_PROVIDER', false));
const model = config.GOOSE_MODEL ?? (await read('GOOSE_MODEL', 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 { FolderOpen, Moon, Sliders, Sun } from 'lucide-react';
import { View } from '../../App'; import { View } from '../../App';
import { useConfig } from '../ConfigContext'; import { useConfig } from '../ConfigContext';
import { toastService } from '../../toasts';
interface VersionInfo { interface VersionInfo {
current_version: string; current_version: string;
@@ -262,7 +263,7 @@ export default function MoreMenu({
await remove('GOOSE_PROVIDER', false); await remove('GOOSE_PROVIDER', false);
await remove('GOOSE_MODEL', false); await remove('GOOSE_MODEL', false);
setOpen(false); setOpen(false);
window.electron.createChatWindow(); setView('welcome');
}} }}
danger danger
subtitle="Clear selected model and restart (alpha)" subtitle="Clear selected model and restart (alpha)"

View File

@@ -76,7 +76,7 @@ export async function extensionApiCall(
} catch (error) { } catch (error) {
// Final catch-all error handler // Final catch-all error handler
toastService.dismiss(toastId); 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({ toastService.error({
title: extensionName, title: extensionName,
msg: msg, msg: msg,

View File

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

View File

@@ -4,13 +4,12 @@ import ModelSettingsButtons from './subcomponents/ModelSettingsButtons';
import { useConfig } from '../../ConfigContext'; import { useConfig } from '../../ConfigContext';
import { toastError } from '../../../toasts'; import { toastError } from '../../../toasts';
import { UNKNOWN_PROVIDER_MSG, UNKNOWN_PROVIDER_TITLE } from './index';
interface ModelsSectionProps { interface ModelsSectionProps {
setView: (view: View) => void; 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) { export default function ModelsSection({ setView }: ModelsSectionProps) {
const [provider, setProvider] = useState<string | null>(null); const [provider, setProvider] = useState<string | null>(null);
const [model, setModel] = useState<string>(''); const [model, setModel] = useState<string>('');

View File

@@ -8,13 +8,13 @@ import type { ExtensionConfig, FixedExtensionEntry } from '../../ConfigContext';
// titles // titles
const CHANGE_MODEL_TOAST_TITLE = 'Model selected'; const CHANGE_MODEL_TOAST_TITLE = 'Model selected';
const START_AGENT_TITLE = 'Initialize agent'; const START_AGENT_TITLE = 'Initialize agent';
const UNKNOWN_PROVIDER_TITLE = 'Provider name lookup'; export const UNKNOWN_PROVIDER_TITLE = 'Provider name lookup';
// errors // errors
const SWITCH_MODEL_AGENT_ERROR_MSG = 'Failed to start agent with selected model'; 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_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 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 // success
const SWITCH_MODEL_SUCCESS_MSG = 'Successfully switched models'; const SWITCH_MODEL_SUCCESS_MSG = 'Successfully switched models';
@@ -114,11 +114,6 @@ export async function getCurrentModelAndProviderForDisplay({
try { try {
metadata = await getProviderMetadata(gooseProvider, getProviders); metadata = await getProviderMetadata(gooseProvider, getProviders);
} catch (error) { } catch (error) {
toastError({
title: UNKNOWN_PROVIDER_TITLE,
msg: UNKNOWN_PROVIDER_MSG,
traceback: error,
});
return { model: gooseModel, provider: gooseProvider }; return { model: gooseModel, provider: gooseProvider };
} }
const providerDisplayName = metadata.display_name; const providerDisplayName = metadata.display_name;

View File

@@ -6,6 +6,8 @@ import { useConfig } from '../../ConfigContext';
import { ProviderDetails } from '../../../api/types.gen'; import { ProviderDetails } from '../../../api/types.gen';
import { initializeSystem } from '../../../utils/providerUtils'; import { initializeSystem } from '../../../utils/providerUtils';
import WelcomeGooseLogo from '../../WelcomeGooseLogo'; import WelcomeGooseLogo from '../../WelcomeGooseLogo';
import { toastService } from '../../../toasts';
import { toast } from 'react-toastify';
interface ProviderSettingsProps { interface ProviderSettingsProps {
onClose: () => void; onClose: () => void;
@@ -56,7 +58,6 @@ export default function ProviderSettings({ onClose, isOnboarding }: ProviderSett
const provider_name = provider.name; const provider_name = provider.name;
const model = provider.metadata.default_model; const model = provider.metadata.default_model;
console.log(`Launching with provider: ${provider.name}`);
try { try {
// update the config // update the config
// set GOOSE_PROVIDER in the config file // set GOOSE_PROVIDER in the config file
@@ -76,6 +77,11 @@ export default function ProviderSettings({ onClose, isOnboarding }: ProviderSett
} catch (error) { } catch (error) {
console.error(`Failed to initialize with provider ${provider_name}:`, 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();
}, },
[onClose, upsert] [onClose, upsert]

View File

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

View File

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