diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index c87aa5a1977d..88a817036cb4 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -256,6 +256,14 @@ export default function App() { <> + `relative min-h-16 mb-4 p-2 rounded-lg + flex justify-between overflow-hidden cursor-pointer + text-textProminentInverse bg-bgStandardInverse dark:bg-bgAppInverse + ` + } + style={{ width: '380px' }} + className="mt-6" position="top-right" autoClose={3000} closeOnClick diff --git a/ui/desktop/src/agent/UpdateAgent.tsx b/ui/desktop/src/agent/UpdateAgent.tsx index fa3cb6432d70..09ad1ce1d8eb 100644 --- a/ui/desktop/src/agent/UpdateAgent.tsx +++ b/ui/desktop/src/agent/UpdateAgent.tsx @@ -4,6 +4,12 @@ import { ExtensionConfig } from '../api'; import { toast } from 'react-toastify'; import React, { useState } from 'react'; import { initializeAgent as startAgent, replaceWithShims } from './utils'; +import { + ToastError, + ToastInfo, + ToastLoading, + ToastSuccess, +} from '../components/settings/models/toasts'; // extensionUpdate = an extension was newly added or updated so we should attempt to add it @@ -25,9 +31,10 @@ export const useAgent = () => { return true; } catch (error) { console.error('Failed to initialize agent:', error); - toast.error( - `Failed to initialize agent: ${error instanceof Error ? error.message : 'Unknown error'}` - ); + ToastError({ + title: 'Failed to initialize agent', + errorMessage: error instanceof Error ? error.message : 'Unknown error', + }); return false; } }; @@ -84,10 +91,14 @@ export const useAgent = () => { try { let toastId; if (!silent) { - toastId = toast.loading(`Adding ${extension.name} extension...`, { - position: 'top-center', + toastId = ToastLoading({ + title: extension.name, + msg: 'Adding extension...', + toastOptions: { position: 'top-center' }, + }); + ToastInfo({ + msg: 'Press the escape key to continue using goose while extension loads', }); - toast.info('Press the escape key to continue using goose while extension loads'); } const response = await fetch(getApiUrl('/extensions/add'), { @@ -108,14 +119,20 @@ export const useAgent = () => { if (response.status === 428) { if (!silent) { if (toastId) toast.dismiss(toastId); - toast.error('Agent is not initialized. Please initialize the agent first.'); + ToastError({ + msg: 'Agent is not initialized. Please initialize the agent first.', + }); } return response; } if (!silent) { if (toastId) toast.dismiss(toastId); - toast.error(`Failed to add ${extension.name} extension: ${errorMsg}`); + ToastError({ + title: extension.name, + msg: 'Failed to add extension', + errorMessage: errorMsg, + }); } return response; } @@ -135,40 +152,34 @@ export const useAgent = () => { if (!data.error) { if (!silent) { if (toastId) toast.dismiss(toastId); - toast.success(`Successfully enabled ${extension.name} extension`); + ToastSuccess({ + title: extension.name, + msg: 'Successfully added extension', + }); } return response; } console.log('Error trying to send a request to the extensions endpoint'); const errorMessage = `Error adding ${extension.name} extension${data.message ? `. ${data.message}` : ''}`; - const ErrorMsg = ({ closeToast }: { closeToast?: () => void }) => ( -
-
Error adding {extension.name} extension
-
- -
-
- ); - console.error(errorMessage); if (toastId) toast.dismiss(toastId); - toast(ErrorMsg, { type: 'error', autoClose: false }); + ToastError({ + title: extension.name, + msg: 'Failed to add extension', + errorMessage: data.message, + }); return response; } catch (error) { console.log('Got some other error'); const errorMessage = `Failed to add ${extension.name} extension: ${error instanceof Error ? error.message : 'Unknown error'}`; console.error(errorMessage); - toast.error(errorMessage, { autoClose: false }); + ToastError({ + title: extension.name, + msg: 'Failed to add extension', + errorMessage: error.message, + }); throw error; } }; diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index 511e18ed0196..23adc33d1775 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -27,6 +27,7 @@ import { getTextContent, createAssistantMessage, } from '../types/message'; +import { ToastSuccess } from './settings/models/toasts'; export interface ChatType { id: string; diff --git a/ui/desktop/src/components/ProviderGrid.tsx b/ui/desktop/src/components/ProviderGrid.tsx index f9735c7586df..850c09a667fa 100644 --- a/ui/desktop/src/components/ProviderGrid.tsx +++ b/ui/desktop/src/components/ProviderGrid.tsx @@ -12,9 +12,9 @@ import { createSelectedModel } from './settings/models/utils'; import { getDefaultModel } from './settings/models/hardcoded_stuff'; import { initializeSystem } from '../utils/providerUtils'; import { getApiUrl, getSecretKey } from '../config'; -import { toast } from 'react-toastify'; import { getActiveProviders, isSecretKey } from './settings/api_keys/utils'; import { BaseProviderGrid, getProviderDescription } from './settings/providers/BaseProviderGrid'; +import { ToastError, ToastSuccess } from './settings/models/toasts'; interface ProviderGridProps { onSubmit?: () => void; @@ -55,9 +55,10 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) { addRecentModel(model); localStorage.setItem('GOOSE_PROVIDER', providerId); - toast.success( - `Selected ${provider.name} provider. Starting Goose with default model: ${getDefaultModel(provider.name.toLowerCase().replace(/ /g, '_'))}.` - ); + ToastSuccess({ + title: provider.name, + msg: `Starting Goose with default model: ${getDefaultModel(provider.name.toLowerCase().replace(/ /g, '_'))}.`, + }); onSubmit?.(); }; @@ -134,11 +135,10 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) { } } - toast.success( - isUpdate - ? `Successfully updated configuration for ${provider}` - : `Successfully added configuration for ${provider}` - ); + ToastSuccess({ + title: provider, + msg: isUpdate ? `Successfully updated configuration` : `Successfully added configuration`, + }); const updatedKeys = await getActiveProviders(); setActiveKeys(updatedKeys); @@ -147,9 +147,11 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) { setSelectedId(null); } catch (error) { console.error('Error handling modal submit:', error); - toast.error( - `Failed to ${providers.find((p) => p.id === selectedId)?.isConfigured ? 'update' : 'add'} configuration for ${provider}` - ); + ToastError({ + title: provider, + msg: `Failed to ${providers.find((p) => p.id === selectedId)?.isConfigured ? 'update' : 'add'} configuration`, + errorMessage: error.message, + }); } }; diff --git a/ui/desktop/src/components/settings/SettingsView.tsx b/ui/desktop/src/components/settings/SettingsView.tsx index 68a77cff0d8d..9e05e83787fb 100644 --- a/ui/desktop/src/components/settings/SettingsView.tsx +++ b/ui/desktop/src/components/settings/SettingsView.tsx @@ -1,6 +1,5 @@ import React, { useState, useEffect } from 'react'; import { ScrollArea } from '../ui/scroll-area'; -import { toast } from 'react-toastify'; import { Settings as SettingsType } from './types'; import { FullExtensionConfig, @@ -16,6 +15,7 @@ import { RecentModelsRadio } from './models/RecentModels'; import { ExtensionItem } from './extensions/ExtensionItem'; import type { View } from '../../App'; import { ModeSelection } from './basic/ModeSelection'; +import { ToastSuccess } from './models/toasts'; const EXTENSIONS_DESCRIPTION = 'The Model Context Protocol (MCP) is a system that allows AI models to securely connect with local or remote resources using standard server setups. It works like a client-server setup and expands AI capabilities using three main components: Prompts, Resources, and Tools.'; @@ -164,7 +164,10 @@ export default function SettingsView({ const response = await removeExtension(extensionBeingConfigured.name, true); if (response.ok) { - toast.success(`Successfully removed ${extensionBeingConfigured.name} extension`); + ToastSuccess({ + title: extensionBeingConfigured.name, + msg: `Successfully removed extension`, + }); // Remove from localstorage setSettings((prev) => ({ diff --git a/ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx b/ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx index 01a42dd60f11..7fd5f8bcad43 100644 --- a/ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx +++ b/ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx @@ -6,6 +6,7 @@ import { FullExtensionConfig } from '../../../extensions'; import { getApiUrl, getSecretKey } from '../../../config'; import { addExtension } from '../../../extensions'; import { toast } from 'react-toastify'; +import { ToastError, ToastSuccess } from '../models/toasts'; interface ConfigureExtensionModalProps { isOpen: boolean; @@ -68,12 +69,19 @@ export function ConfigureBuiltInExtensionModal({ throw new Error('Failed to add system configuration'); } - toast.success(`Successfully configured the ${extension.name} extension`); + ToastSuccess({ + title: extension.name, + msg: `Successfully configured extension`, + }); onSubmit(); onClose(); } catch (error) { console.error('Error configuring extension:', error); - toast.error('Failed to configure extension'); + ToastError({ + title: extension.name, + msg: `Failed to configure the extension`, + errorMessage: error.message, + }); } finally { setIsSubmitting(false); } diff --git a/ui/desktop/src/components/settings/extensions/ConfigureExtensionModal.tsx b/ui/desktop/src/components/settings/extensions/ConfigureExtensionModal.tsx index 0ae8914de87e..dc51bd9f4493 100644 --- a/ui/desktop/src/components/settings/extensions/ConfigureExtensionModal.tsx +++ b/ui/desktop/src/components/settings/extensions/ConfigureExtensionModal.tsx @@ -6,6 +6,7 @@ import { FullExtensionConfig } from '../../../extensions'; import { getApiUrl, getSecretKey } from '../../../config'; import { addExtension } from '../../../extensions'; import { toast } from 'react-toastify'; +import { ToastError, ToastSuccess } from '../models/toasts'; interface ConfigureExtensionModalProps { isOpen: boolean; @@ -70,12 +71,19 @@ export function ConfigureExtensionModal({ throw new Error('Failed to add system configuration'); } - toast.success(`Successfully configured the ${extension.name} extension`); + ToastSuccess({ + title: extension.name, + msg: `Successfully configured extension`, + }); onSubmit(); onClose(); } catch (error) { console.error('Error configuring extension:', error); - toast.error('Failed to configure extension'); + ToastError({ + title: extension.name, + msg: `Failed to configure extension`, + errorMessage: error.message, + }); } finally { setIsSubmitting(false); } diff --git a/ui/desktop/src/components/settings/extensions/ManualExtensionModal.tsx b/ui/desktop/src/components/settings/extensions/ManualExtensionModal.tsx index 97259c98af6e..ca747687645e 100644 --- a/ui/desktop/src/components/settings/extensions/ManualExtensionModal.tsx +++ b/ui/desktop/src/components/settings/extensions/ManualExtensionModal.tsx @@ -7,6 +7,7 @@ import { toast } from 'react-toastify'; import Select from 'react-select'; import { createDarkSelectStyles, darkSelectTheme } from '../../ui/select-styles'; import { getApiUrl, getSecretKey } from '../../../config'; +import { ToastError } from '../models/toasts'; interface ManualExtensionModalProps { isOpen: boolean; @@ -38,22 +39,22 @@ export function ManualExtensionModal({ isOpen, onClose, onSubmit }: ManualExtens e.preventDefault(); if (!formData.id || !formData.name || !formData.description) { - toast.error('Please fill in all required fields'); + ToastError({ title: 'Please fill in all required fields' }); return; } if (formData.type === 'stdio' && !formData.commandInput) { - toast.error('Command is required for stdio type'); + ToastError({ title: 'Command is required for stdio type' }); return; } if (formData.type === 'sse' && !formData.uri) { - toast.error('URI is required for SSE type'); + ToastError({ title: 'URI is required for SSE type' }); return; } if (formData.type === 'builtin' && !formData.name) { - toast.error('Name is required for builtin type'); + ToastError({ title: 'Name is required for builtin type' }); return; } @@ -98,7 +99,7 @@ export function ManualExtensionModal({ isOpen, onClose, onSubmit }: ManualExtens resetForm(); } catch (error) { console.error('Error configuring extension:', error); - toast.error('Failed to configure extension'); + ToastError({ title: 'Failed to configure extension', errorMessage: error.message }); } }; diff --git a/ui/desktop/src/components/settings/models/toasts.tsx b/ui/desktop/src/components/settings/models/toasts.tsx index 08e03cad57f4..bbabbaf8ba96 100644 --- a/ui/desktop/src/components/settings/models/toasts.tsx +++ b/ui/desktop/src/components/settings/models/toasts.tsx @@ -1,37 +1,72 @@ -import { toast } from 'react-toastify'; +import { toast, ToastOptions } from 'react-toastify'; import React from 'react'; -import { Model } from './ModelContext'; -export function ToastSuccessModelSwitch(model: Model) { +const commonToastOptions: ToastOptions = { + position: 'top-right', + closeButton: false, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, +}; + +type ToastSuccessProps = { title?: string; msg?: string; toastOptions?: ToastOptions }; +export function ToastSuccess({ title, msg, toastOptions = {} }: ToastSuccessProps) { return toast.success(
- Model Changed -
Switched to {model.alias ?? model.name}
+ {title ? {title} : null} + {title ?
{msg}
: null}
, - { - position: 'top-right', - autoClose: 5000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - } + { ...commonToastOptions, autoClose: 3000, ...toastOptions } ); } -export function ToastFailureGeneral(msg?: string) { +type ToastErrorProps = { + title?: string; + msg?: string; + errorMessage?: string; + toastOptions?: ToastOptions; +}; +export function ToastError({ title, msg, errorMessage, toastOptions }: ToastErrorProps) { return toast.error( +
+
+ {title ? {title} : null} + {msg ?
{msg}
: null} +
+
+ {errorMessage ? ( + + ) : null} +
+
, + { ...commonToastOptions, autoClose: errorMessage ? false : 5000, ...toastOptions } + ); +} + +type ToastLoadingProps = { title?: string; msg?: string; toastOptions?: ToastOptions }; +export function ToastLoading({ title, msg, toastOptions }: ToastLoadingProps) { + return toast.loading( +
+ {title ? {title} : null} + {title ?
{msg}
: null} +
, + { ...commonToastOptions, autoClose: false, ...toastOptions } + ); +} + +type ToastInfoProps = { title?: string; msg?: string; toastOptions?: ToastOptions }; +export function ToastInfo({ title, msg, toastOptions }: ToastInfoProps) { + return toast.info(
- Error -
{msg}
+ {title ? {title} : null} + {msg ?
{msg}
: null}
, - { - position: 'top-right', - autoClose: 3000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - } + { ...commonToastOptions, ...toastOptions } ); } diff --git a/ui/desktop/src/components/settings/models/utils.tsx b/ui/desktop/src/components/settings/models/utils.tsx index 8cb71e0a27b5..3f0b22590675 100644 --- a/ui/desktop/src/components/settings/models/utils.tsx +++ b/ui/desktop/src/components/settings/models/utils.tsx @@ -2,7 +2,7 @@ import { useModel } from './ModelContext'; // Import the useModel hook import { Model } from './ModelContext'; import { useMemo } from 'react'; import { gooseModels } from './GooseModels'; -import { ToastFailureGeneral, ToastSuccessModelSwitch } from './toasts'; +import { ToastError, ToastSuccess } from './toasts'; import { initializeSystem } from '../../../utils/providerUtils'; import { useRecentModels } from './RecentModels'; @@ -32,12 +32,19 @@ export function useHandleModelSelection() { console.log(`[${componentName}] Switched to model: ${model.name} (${model.provider})`); // Display a success toast notification - ToastSuccessModelSwitch(model); + ToastSuccess({ + title: 'Model changed', + msg: `Switched to ${model.alias ?? model.name}`, + }); } catch (error) { // Handle errors gracefully console.error(`[${componentName}] Failed to switch model:`, error); // Display an error toast notification - ToastFailureGeneral(`Failed to switch to model: ${model.name}`); + ToastError({ + title: model.name, + msg: `Failed to switch to model`, + errorMessage: error.message, + }); } }; } diff --git a/ui/desktop/src/components/settings/providers/ConfigureProvidersGrid.tsx b/ui/desktop/src/components/settings/providers/ConfigureProvidersGrid.tsx index b5194943d64e..0968de2a0b7b 100644 --- a/ui/desktop/src/components/settings/providers/ConfigureProvidersGrid.tsx +++ b/ui/desktop/src/components/settings/providers/ConfigureProvidersGrid.tsx @@ -8,6 +8,7 @@ import { toast } from 'react-toastify'; import { getActiveProviders, isSecretKey } from '../api_keys/utils'; import { useModel } from '../models/ModelContext'; import { Button } from '../../ui/button'; +import { ToastError, ToastSuccess } from '../models/toasts'; function ConfirmationModal({ message, onConfirm, onCancel }) { return ( @@ -141,11 +142,10 @@ export function ConfigureProvidersGrid() { } } - toast.success( - isUpdate - ? `Successfully updated configuration for ${provider}` - : `Successfully added configuration for ${provider}` - ); + ToastSuccess({ + title: provider, + msg: isUpdate ? `Successfully updated configuration` : `Successfully added configuration`, + }); const updatedKeys = await getActiveProviders(); setActiveKeys(updatedKeys); @@ -155,9 +155,11 @@ export function ConfigureProvidersGrid() { setModalMode('setup'); } catch (error) { console.error('Error handling modal submit:', error); - toast.error( - `Failed to ${providers.find((p) => p.id === selectedForSetup)?.isConfigured ? 'update' : 'add'} configuration for ${provider}` - ); + ToastError({ + title: provider, + msg: `Failed to ${providers.find((p) => p.id === selectedForSetup)?.isConfigured ? 'update' : 'add'} configuration`, + errorMessage: error.message, + }); } }; @@ -178,9 +180,8 @@ export function ConfigureProvidersGrid() { try { // Check if the selected provider is currently active if (currentModel?.provider === providerToDelete.name) { - toast.error( - `Cannot delete the configuration for ${providerToDelete.name} because it's the provider of the current model (${currentModel.name}). Please switch to a different model first.` - ); + 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 }); setIsConfirmationOpen(false); return; } @@ -208,13 +209,20 @@ export function ConfigureProvidersGrid() { } console.log('Configuration deleted successfully.'); - toast.success(`Successfully deleted configuration for ${providerToDelete.name}`); + ToastSuccess({ + title: providerToDelete.name, + msg: 'Successfully deleted configuration', + }); const updatedKeys = await getActiveProviders(); setActiveKeys(updatedKeys); } catch (error) { console.error('Error deleting configuration:', error); - toast.error(`Unable to delete configuration for ${providerToDelete.name}`); + ToastError({ + title: providerToDelete.name, + msg: 'Failed to delete configuration', + errorMessage: error.message, + }); } setIsConfirmationOpen(false); }; diff --git a/ui/desktop/src/components/settings_v2/models/AddModelModal.tsx b/ui/desktop/src/components/settings_v2/models/AddModelModal.tsx index 0ff570a19b58..1f2b420a0a4c 100644 --- a/ui/desktop/src/components/settings_v2/models/AddModelModal.tsx +++ b/ui/desktop/src/components/settings_v2/models/AddModelModal.tsx @@ -7,7 +7,7 @@ import { QUICKSTART_GUIDE_URL } from '../providers/modal/constants'; import { Input } from '../../ui/input'; import { Select } from '../../ui/Select'; import { useConfig } from '../../ConfigContext'; -import { ToastFailureGeneral, ToastSuccessModelSwitch } from '../../settings/models/toasts'; +import { ToastError, ToastSuccess } from '../../settings/models/toasts'; import { initializeSystem } from '../../../../src/utils/providerUtils'; const ModalButtons = ({ onSubmit, onCancel }) => ( @@ -43,10 +43,16 @@ export const AddModelModal = ({ onClose }: AddModelModalProps) => { await upsert('GOOSE_PROVIDER', provider, false); await upsert('GOOSE_MODEL', modelName, false); await initializeSystem(provider, modelName); - ToastSuccessModelSwitch({ provider, name: modelName }); + ToastSuccess({ + title: 'Model changed', + msg: `Switched to ${modelName}.`, + }); onClose(); } catch (e) { - ToastFailureGeneral(e.message); + ToastError({ + title: 'Failed to add model', + errorMessage: e.message, + }); } }; diff --git a/ui/desktop/src/extensions.tsx b/ui/desktop/src/extensions.tsx index 6df915e1b9ec..5e7742e1d9c2 100644 --- a/ui/desktop/src/extensions.tsx +++ b/ui/desktop/src/extensions.tsx @@ -5,6 +5,8 @@ import { type SettingsViewOptions } from './components/settings/SettingsView'; import { toast } from 'react-toastify'; import builtInExtensionsData from './built-in-extensions.json'; +import { ToastError, ToastLoading, ToastSuccess } from './components/settings/models/toasts'; +import { Toast } from 'react-toastify/dist/components'; // Hardcoded default extension timeout in seconds export const DEFAULT_EXTENSION_TIMEOUT = 300; @@ -82,7 +84,7 @@ export async function addExtension( }; let toastId; - if (!silent) toastId = toast.loading(`Adding ${extension.name} extension...`); + if (!silent) toastId = ToastLoading({ title: extension.name, msg: 'Adding extension...' }); const response = await fetch(getApiUrl('/extensions/add'), { method: 'POST', @@ -98,38 +100,31 @@ export async function addExtension( if (!data.error) { if (!silent) { if (toastId) toast.dismiss(toastId); - toast.success(`Successfully enabled ${extension.name} extension`); + ToastSuccess({ title: extension.name, msg: `Successfully enabled extension` }); } return response; } - const errorMessage = `Error adding ${extension.name} extension ${data.message ? `. ${data.message}` : ''}`; - const ErrorMsg = ({ closeToast }: { closeToast?: () => void }) => ( -
-
Error adding {extension.name} extension
-
- -
-
- ); - + const errorMessage = `Error adding extension`; console.error(errorMessage); if (toastId) toast.dismiss(toastId); - toast(ErrorMsg, { type: 'error', autoClose: false }); + ToastError({ + title: extension.name, + msg: errorMessage, + errorMessage: data.message, + toastOptions: { autoClose: false }, + }); return response; } catch (error) { const errorMessage = `Failed to add ${extension.name} extension: ${error instanceof Error ? error.message : 'Unknown error'}`; console.error(errorMessage); - toast.error(errorMessage, { autoClose: false }); + ToastError({ + title: extension.name, + msg: 'Failed to add extension', + errorMessage: error.message, + toastOptions: { autoClose: false }, + }); throw error; } } @@ -149,19 +144,29 @@ export async function removeExtension(name: string, silent: boolean = false): Pr if (!data.error) { if (!silent) { - toast.success(`Successfully disabled ${name} extension`); + ToastSuccess({ title: name, msg: 'Successfully disabled extension' }); } return response; } const errorMessage = `Error removing ${name} extension${data.message ? `. ${data.message}` : ''}`; console.error(errorMessage); - toast.error(errorMessage, { autoClose: false }); + ToastError({ + title: name, + msg: 'Error removing extension', + errorMessage: data.message, + toastOptions: { autoClose: false }, + }); return response; } catch (error) { const errorMessage = `Failed to remove ${name} extension: ${error instanceof Error ? error.message : 'Unknown error'}`; console.error(errorMessage); - toast.error(errorMessage, { autoClose: false }); + ToastError({ + title: name, + msg: 'Error removing extension', + errorMessage: error.message, + toastOptions: { autoClose: false }, + }); throw error; } } @@ -241,7 +246,12 @@ function envVarsRequired(config: ExtensionConfig) { } function handleError(message: string, shouldThrow = false): void { - toast.error(message, { autoClose: false }); + ToastError({ + title: 'Failed to install extension', + msg: message, + errorMessage: message, + toastOptions: { autoClose: false }, + }); console.error(message); if (shouldThrow) { throw new Error(message); @@ -253,19 +263,14 @@ export async function addExtensionFromDeepLink( setView: (view: View, options: SettingsViewOptions) => void ) { if (!url.startsWith('goose://extension')) { - handleError( - 'Failed to install extension: Invalid URL: URL must use the goose://extension scheme' - ); + handleError('Invalid URL: URL must use the goose://extension scheme'); return; } const parsedUrl = new URL(url); if (parsedUrl.protocol !== 'goose:') { - handleError( - 'Failed to install extension: Invalid protocol: URL must use the goose:// scheme', - true - ); + handleError('Invalid protocol: URL must use the goose:// scheme', true); } // Check that all required fields are present and not empty @@ -274,10 +279,7 @@ export async function addExtensionFromDeepLink( for (const field of requiredFields) { const value = parsedUrl.searchParams.get(field); if (!value || value.trim() === '') { - handleError( - `Failed to install extension: The link is missing required field '${field}'`, - true - ); + handleError(`The link is missing required field '${field}'`, true); } } @@ -298,10 +300,7 @@ export async function addExtensionFromDeepLink( // Check for security risk with npx -c command const args = parsedUrl.searchParams.getAll('arg'); if (cmd === 'npx' && args.includes('-c')) { - handleError( - 'Failed to install extension: npx with -c argument can lead to code injection', - true - ); + handleError('npx with -c argument can lead to code injection', true); } const envList = parsedUrl.searchParams.getAll('env'); diff --git a/ui/desktop/src/styles/main.css b/ui/desktop/src/styles/main.css index ed69d4e20a87..c461961bfa65 100644 --- a/ui/desktop/src/styles/main.css +++ b/ui/desktop/src/styles/main.css @@ -98,6 +98,8 @@ --background-prominent: var(--grey-80); --background-standard: var(--grey-90); --background-subtle: var(--grey-95); + --background-app-inverse: var(--constant-black); + --background-standard-inverse: var(--dark-grey-25); --border-divider: var(--grey-90); --border-inverse: var(--constant-white); @@ -116,12 +118,15 @@ --text-prominent: var(--grey-10); --text-standard: var(--grey-20); --text-subtle: var(--grey-50); + --text-prominent-inverse: var(--constant-white); &.dark { --background-app: var(--constant-black); --background-prominent: var(--dark-grey-40); --background-standard: var(--dark-grey-25); --background-subtle: var(--dark-grey-15); + --background-app-inverse: var(--constant-white); + --background-prominent-inverse: var(--grey-80); --border-divider: var(--dark-grey-25); --border-inverse: var(--constant-black); @@ -140,6 +145,7 @@ --text-prominent: var(--constant-white); --text-standard: var(--dark-grey-90); --text-subtle: var(--dark-grey-60); + --text-prominent-inverse: var(--grey-20); } /* end arcade colors */ } diff --git a/ui/desktop/tailwind.config.ts b/ui/desktop/tailwind.config.ts index 6d842b79c078..0309b53ce272 100644 --- a/ui/desktop/tailwind.config.ts +++ b/ui/desktop/tailwind.config.ts @@ -53,6 +53,8 @@ export default { bgSubtle: 'var(--background-subtle)', bgStandard: 'var(--background-standard)', bgProminent: 'var(--background-prominent)', + bgAppInverse: 'var(--background-app-inverse)', + bgStandardInverse: 'var(--background-standard-inverse)', borderSubtle: 'var(--border-subtle)', borderStandard: 'var(--border-standard)', @@ -61,6 +63,7 @@ export default { textStandard: 'var(--text-standard)', textSubtle: 'var(--text-subtle)', textPlaceholder: 'var(--text-placeholder)', + textProminentInverse: 'var(--text-prominent-inverse)', iconProminent: 'var(--icon-prominent)', iconStandard: 'var(--icon-standard)',