From 710e7f35ed27c1e483a552649a486f9af6027c51 Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Wed, 28 Jan 2026 10:29:10 -0500 Subject: [PATCH] Use errorMessage (#6749) Co-authored-by: Douwe Osinga --- ui/desktop/src/App.tsx | 4 ++-- .../src/components/ExtensionInstallModal.tsx | 10 +++------- .../src/components/MCPUIResourceRenderer.tsx | 5 +++-- .../src/components/McpApps/McpAppRenderer.tsx | 3 ++- .../src/components/ModelAndProviderContext.tsx | 6 ++---- ui/desktop/src/components/OllamaSetup.tsx | 4 ++-- ui/desktop/src/components/alerts/AlertBox.tsx | 3 ++- ui/desktop/src/components/apps/AppsView.tsx | 9 +++++---- .../src/components/apps/StandaloneAppView.tsx | 3 ++- .../recipes/CreateEditRecipeModal.tsx | 9 +++++---- .../recipes/CreateRecipeFromSessionModal.tsx | 9 +++++---- .../src/components/recipes/ImportRecipeForm.tsx | 9 +++++---- .../src/components/recipes/RecipesView.tsx | 15 ++++++++------- .../components/schedule/ScheduleDetailView.tsx | 17 +++++++++-------- .../src/components/schedule/SchedulesView.tsx | 17 ++++++++--------- .../components/sessions/SessionHistoryView.tsx | 5 +++-- .../src/components/sessions/SessionListView.tsx | 13 ++++++------- .../components/sessions/SessionsInsights.tsx | 3 ++- .../components/settings/app/UpdateSection.tsx | 3 ++- .../settings/config/ConfigSettings.tsx | 3 ++- .../settings/models/modelInterface.ts | 4 +++- ui/desktop/src/hooks/useFileDrop.ts | 4 ++-- ui/desktop/src/hooks/useWhisper.ts | 5 ++--- ui/desktop/src/main.ts | 5 ++--- ui/desktop/src/sessionLinks.ts | 8 +++++--- ui/desktop/src/utils/autoUpdater.ts | 13 +++++++------ ui/desktop/src/utils/githubUpdater.ts | 10 +++++----- ui/desktop/src/utils/ollamaDetection.ts | 4 +++- ui/desktop/src/utils/sessionCache.ts | 6 ++---- 29 files changed, 109 insertions(+), 100 deletions(-) diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 989b7cf0..52983918 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -419,7 +419,7 @@ export function AppInner() { } catch (error) { console.error('Error sending reactReady:', error); setFatalError( - `React ready notification failed: ${error instanceof Error ? error.message : 'Unknown error'}` + `React ready notification failed: ${errorMessage(error, 'Unknown error')}` ); } }, []); @@ -445,7 +445,7 @@ export function AppInner() { const shareToken = link.replace('goose://sessions/', ''); const options = { sessionDetails: null, - error: error instanceof Error ? error.message : 'Unknown error', + error: errorMessage(error, 'Unknown error'), shareToken, }; navigate('/shared-session', { state: options }); diff --git a/ui/desktop/src/components/ExtensionInstallModal.tsx b/ui/desktop/src/components/ExtensionInstallModal.tsx index 3b5153ee..310fa27f 100644 --- a/ui/desktop/src/components/ExtensionInstallModal.tsx +++ b/ui/desktop/src/components/ExtensionInstallModal.tsx @@ -15,6 +15,7 @@ import type { ExtensionConfig } from '../api/types.gen'; import { View, ViewOptions } from '../utils/navigationUtils'; import { useConfig } from './ConfigContext'; import { toastService } from '../toasts'; +import { errorMessage } from '../utils/conversionUtils'; type ModalType = 'blocked' | 'untrusted' | 'trusted'; @@ -208,7 +209,7 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal console.error('Error processing extension request:', error); setModalState((prev) => ({ ...prev, - error: error instanceof Error ? error.message : 'Unknown error', + error: errorMessage(error, 'Unknown error'), })); } finally { processingLinkRef.current = null; @@ -248,16 +249,11 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal } else { throw new Error('addExtension function not provided to component'); } - - // Only dismiss modal after successful installation dismissModal(); } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Installation failed'; - console.error('Extension installation failed:', error); - setModalState((prev) => ({ ...prev, - error: errorMessage, + error: errorMessage(error, 'Installation failed'), isPending: false, })); } diff --git a/ui/desktop/src/components/MCPUIResourceRenderer.tsx b/ui/desktop/src/components/MCPUIResourceRenderer.tsx index ffd87e8b..348a6829 100644 --- a/ui/desktop/src/components/MCPUIResourceRenderer.tsx +++ b/ui/desktop/src/components/MCPUIResourceRenderer.tsx @@ -12,6 +12,7 @@ import { useState, useEffect } from 'react'; import { toast } from 'react-toastify'; import { EmbeddedResource } from '../api'; import { useTheme } from '../contexts/ThemeContext'; +import { errorMessage } from '../utils/conversionUtils'; interface MCPUIResourceRendererProps { content: EmbeddedResource & { type: 'resource' }; @@ -154,7 +155,7 @@ export default function MCPUIResourceRenderer({ error: { code: UIActionErrorCode.PROMPT_FAILED, message: 'Failed to send prompt to chat', - details: error instanceof Error ? error.message : error, + details: errorMessage(error), }, }; } @@ -218,7 +219,7 @@ export default function MCPUIResourceRenderer({ error: { code: UIActionErrorCode.NAVIGATION_FAILED, message: `Unexpected error opening URL: ${url}`, - details: error instanceof Error ? error.message : error, + details: errorMessage(error), }, }; } diff --git a/ui/desktop/src/components/McpApps/McpAppRenderer.tsx b/ui/desktop/src/components/McpApps/McpAppRenderer.tsx index 3d337ed1..a69af118 100644 --- a/ui/desktop/src/components/McpApps/McpAppRenderer.tsx +++ b/ui/desktop/src/components/McpApps/McpAppRenderer.tsx @@ -21,6 +21,7 @@ import { import { cn } from '../../utils'; import { DEFAULT_IFRAME_HEIGHT } from './utils'; import { readResource, callTool } from '../../api'; +import { errorMessage } from '../../utils/conversionUtils'; interface McpAppRendererProps { resourceUri: string; @@ -92,7 +93,7 @@ export default function McpAppRenderer({ } } catch (err) { if (!cachedHtml) { - setError(err instanceof Error ? err.message : 'Failed to load resource'); + setError(errorMessage(err, 'Failed to load resource')); } else { console.warn('Failed to fetch fresh resource, using cached version:', err); } diff --git a/ui/desktop/src/components/ModelAndProviderContext.tsx b/ui/desktop/src/components/ModelAndProviderContext.tsx index e8e37089..04e279e9 100644 --- a/ui/desktop/src/components/ModelAndProviderContext.tsx +++ b/ui/desktop/src/components/ModelAndProviderContext.tsx @@ -3,15 +3,13 @@ import { toastError, toastSuccess } from '../toasts'; import Model, { getProviderMetadata } from './settings/models/modelInterface'; import { ProviderMetadata, setConfigProvider, updateAgentProvider } from '../api'; import { useConfig } from './ConfigContext'; +import { errorMessage } from '../utils/conversionUtils'; import { getModelDisplayName, getProviderDisplayName, } from './settings/models/predefinedModelsUtils'; -// titles export const UNKNOWN_PROVIDER_TITLE = 'Provider name lookup'; - -// errors export const UNKNOWN_PROVIDER_MSG = 'Unknown provider in config -- please inspect your config.yaml'; // success @@ -80,7 +78,7 @@ export const ModelAndProviderProvider: React.FC = toastError({ title: `${providerName}/${modelName} failed`, msg: `${error}`, - traceback: error instanceof Error ? error.message : String(error), + traceback: errorMessage(error), }); } }, []); diff --git a/ui/desktop/src/components/OllamaSetup.tsx b/ui/desktop/src/components/OllamaSetup.tsx index 40063ebd..e9f3130d 100644 --- a/ui/desktop/src/components/OllamaSetup.tsx +++ b/ui/desktop/src/components/OllamaSetup.tsx @@ -9,9 +9,9 @@ import { getPreferredModel, type PullProgress, } from '../utils/ollamaDetection'; -//import { initializeSystem } from '../utils/providerUtils'; import { toastService } from '../toasts'; import { Ollama } from './icons'; +import { errorMessage } from '../utils/conversionUtils'; interface OllamaSetupProps { onSuccess: () => void; @@ -120,7 +120,7 @@ export function OllamaSetup({ onSuccess, onCancel }: OllamaSetupProps) { console.error('Failed to connect to Ollama:', error); toastService.error({ title: 'Connection Failed', - msg: `Failed to connect to Ollama: ${error instanceof Error ? error.message : String(error)}`, + msg: `Failed to connect to Ollama: ${errorMessage(error)}`, traceback: error instanceof Error ? error.stack || '' : '', }); setIsConnecting(false); diff --git a/ui/desktop/src/components/alerts/AlertBox.tsx b/ui/desktop/src/components/alerts/AlertBox.tsx index e28ac2ce..9ab80c26 100644 --- a/ui/desktop/src/components/alerts/AlertBox.tsx +++ b/ui/desktop/src/components/alerts/AlertBox.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { IoIosCloseCircle, IoIosWarning, IoIosInformationCircle } from 'react-icons/io'; import { FaPencilAlt, FaSave } from 'react-icons/fa'; import { cn } from '../../utils'; +import { errorMessage } from '../../utils/conversionUtils'; import { Alert, AlertType } from './types'; import { upsertConfig } from '../../api'; import { useConfig } from '../ConfigContext'; @@ -90,7 +91,7 @@ export const AlertBox = ({ alert, className }: AlertBoxProps) => { } catch (error) { console.error('Error saving threshold:', error); window.alert( - `Failed to save threshold: ${error instanceof Error ? error.message : 'Unknown error'}` + `Failed to save threshold: ${errorMessage(error, 'Unknown error')}` ); } finally { setIsSaving(false); diff --git a/ui/desktop/src/components/apps/AppsView.tsx b/ui/desktop/src/components/apps/AppsView.tsx index 69863b64..41f1d0dc 100644 --- a/ui/desktop/src/components/apps/AppsView.tsx +++ b/ui/desktop/src/components/apps/AppsView.tsx @@ -5,6 +5,7 @@ import { Download, Play, Upload } from 'lucide-react'; import { exportApp, GooseApp, importApp, listApps } from '../../api'; import { useChatContext } from '../../contexts/ChatContext'; import { formatAppName } from '../../utils/conversionUtils'; +import { errorMessage } from '../../utils/conversionUtils'; const GridLayout = ({ children }: { children: React.ReactNode }) => { return ( @@ -63,7 +64,7 @@ export default function AppsView() { console.warn('Failed to refresh apps:', err); // Don't set error if we already have cached apps if (apps.length === 0) { - setError(err instanceof Error ? err.message : 'Failed to load apps'); + setError(errorMessage(err, 'Failed to load apps')); } } }; @@ -115,7 +116,7 @@ export default function AppsView() { } catch (err) { // Only set error if we don't have apps to show if (apps.length === 0) { - setError(err instanceof Error ? err.message : 'Failed to load apps'); + setError(errorMessage(err, 'Failed to load apps')); } } finally { setLoading(false); @@ -151,7 +152,7 @@ export default function AppsView() { } } catch (err) { console.error('Failed to export app:', err); - setError(err instanceof Error ? err.message : 'Failed to export app'); + setError(errorMessage(err, 'Failed to export app')); } }; @@ -179,7 +180,7 @@ export default function AppsView() { setError(null); } catch (err) { console.error('Failed to import app:', err); - setError(err instanceof Error ? err.message : 'Failed to import app'); + setError(errorMessage(err, 'Failed to import app')); } event.target.value = ''; }; diff --git a/ui/desktop/src/components/apps/StandaloneAppView.tsx b/ui/desktop/src/components/apps/StandaloneAppView.tsx index 77ba0120..7db925d5 100644 --- a/ui/desktop/src/components/apps/StandaloneAppView.tsx +++ b/ui/desktop/src/components/apps/StandaloneAppView.tsx @@ -3,6 +3,7 @@ import { useSearchParams } from 'react-router-dom'; import McpAppRenderer from '../McpApps/McpAppRenderer'; import { startAgent, resumeAgent, listApps, stopAgent } from '../../api'; import { formatAppName } from '../../utils/conversionUtils'; +import { errorMessage } from '../../utils/conversionUtils'; export default function StandaloneAppView() { const [searchParams] = useSearchParams(); @@ -78,7 +79,7 @@ export default function StandaloneAppView() { } catch (err) { console.error('Failed to initialize session:', err); if (!cachedHtml) { - setError(err instanceof Error ? err.message : 'Failed to initialize session'); + setError(errorMessage(err, 'Failed to initialize session')); setLoading(false); } } diff --git a/ui/desktop/src/components/recipes/CreateEditRecipeModal.tsx b/ui/desktop/src/components/recipes/CreateEditRecipeModal.tsx index 67cbdb54..5f342e06 100644 --- a/ui/desktop/src/components/recipes/CreateEditRecipeModal.tsx +++ b/ui/desktop/src/components/recipes/CreateEditRecipeModal.tsx @@ -11,6 +11,7 @@ import { RecipeFormFields } from './shared/RecipeFormFields'; import { RecipeFormData } from './shared/recipeFormSchema'; import { toastSuccess, toastError } from '../../toasts'; import { saveRecipe } from '../../recipe/recipe_management'; +import { errorMessage } from '../../utils/conversionUtils'; interface CreateEditRecipeModalProps { isOpen: boolean; @@ -272,8 +273,8 @@ export default function CreateEditRecipeModal({ toastError({ title: 'Save Failed', - msg: `Failed to save recipe: ${error instanceof Error ? error.message : 'Unknown error'}`, - traceback: error instanceof Error ? error.message : String(error), + msg: `Failed to save recipe: ${errorMessage(error, 'Unknown error')}`, + traceback: errorMessage(error), }); } finally { setIsSaving(false); @@ -317,8 +318,8 @@ export default function CreateEditRecipeModal({ toastError({ title: 'Save and Run Failed', - msg: `Failed to save and run recipe: ${error instanceof Error ? error.message : 'Unknown error'}`, - traceback: error instanceof Error ? error.message : String(error), + msg: `Failed to save and run recipe: ${errorMessage(error, 'Unknown error')}`, + traceback: errorMessage(error), }); } finally { setIsSaving(false); diff --git a/ui/desktop/src/components/recipes/CreateRecipeFromSessionModal.tsx b/ui/desktop/src/components/recipes/CreateRecipeFromSessionModal.tsx index 61cb735b..a16051a8 100644 --- a/ui/desktop/src/components/recipes/CreateRecipeFromSessionModal.tsx +++ b/ui/desktop/src/components/recipes/CreateRecipeFromSessionModal.tsx @@ -10,6 +10,7 @@ import { createRecipe } from '../../api/sdk.gen'; import { RecipeParameter } from './shared/recipeFormSchema'; import { toastError } from '../../toasts'; import { saveRecipe } from '../../recipe/recipe_management'; +import { errorMessage } from '../../utils/conversionUtils'; interface CreateRecipeFromSessionModalProps { isOpen: boolean; @@ -202,10 +203,10 @@ export default function CreateRecipeFromSessionModal({ console.error('Failed to create recipe:', error); toastError({ title: 'Failed to create recipe', - msg: - error instanceof Error - ? error.message - : 'An unexpected error occurred while creating the recipe. Please try again.', + msg: errorMessage( + error, + 'An unexpected error occurred while creating the recipe. Please try again.' + ), }); } finally { setIsCreating(false); diff --git a/ui/desktop/src/components/recipes/ImportRecipeForm.tsx b/ui/desktop/src/components/recipes/ImportRecipeForm.tsx index c9c4de9f..21c36b38 100644 --- a/ui/desktop/src/components/recipes/ImportRecipeForm.tsx +++ b/ui/desktop/src/components/recipes/ImportRecipeForm.tsx @@ -10,6 +10,7 @@ import { useEscapeKey } from '../../hooks/useEscapeKey'; import { getRecipeJsonSchema } from '../../recipe/validation'; import { saveRecipe } from '../../recipe/recipe_management'; import { parseRecipe } from '../../api'; +import { errorMessage } from '../../utils/conversionUtils'; interface ImportRecipeFormProps { isOpen: boolean; @@ -138,8 +139,8 @@ export default function ImportRecipeForm({ isOpen, onClose, onSuccess }: ImportR toastError({ title: 'Import Failed', - msg: `Failed to import recipe: ${error instanceof Error ? error.message : 'Unknown error'}`, - traceback: error instanceof Error ? error.message : String(error), + msg: `Failed to import recipe: ${errorMessage(error, 'Unknown error')}`, + traceback: errorMessage(error), }); } finally { setImporting(false); @@ -167,7 +168,7 @@ export default function ImportRecipeForm({ isOpen, onClose, onSuccess }: ImportR } catch (error) { toastError({ title: 'Invalid Deeplink', - msg: `The deeplink format is invalid: ${error instanceof Error ? error.message : 'Unknown error'}`, + msg: `The deeplink format is invalid: ${errorMessage(error, 'Unknown error')}`, }); } } @@ -183,7 +184,7 @@ export default function ImportRecipeForm({ isOpen, onClose, onSuccess }: ImportR } catch (error) { toastError({ title: 'Invalid Recipe File', - msg: error instanceof Error ? error.message : 'Unknown error', + msg: errorMessage(error, 'Unknown error'), }); } } diff --git a/ui/desktop/src/components/recipes/RecipesView.tsx b/ui/desktop/src/components/recipes/RecipesView.tsx index 65e6aaff..64fe60f6 100644 --- a/ui/desktop/src/components/recipes/RecipesView.tsx +++ b/ui/desktop/src/components/recipes/RecipesView.tsx @@ -57,6 +57,7 @@ import { DropdownMenuSeparator, } from '../ui/dropdown-menu'; import { getSearchShortcutText } from '../../utils/keyboardShortcuts'; +import { errorMessage } from '../../utils/conversionUtils'; export default function RecipesView() { const setView = useNavigation(); @@ -130,7 +131,7 @@ export default function RecipesView() { const recipeManifestResponses = await listSavedRecipes(); setSavedRecipes(recipeManifestResponses); } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load recipes'); + setError(errorMessage(err, 'Failed to load recipes')); console.error('Failed to load saved recipes:', err); } finally { setLoading(false); @@ -154,7 +155,7 @@ export default function RecipesView() { }); } catch (error) { console.error('Failed to load recipe:', error); - const errorMsg = error instanceof Error ? error.message : 'Failed to load recipe'; + const errorMsg = errorMessage(error, 'Failed to load recipe'); trackRecipeStarted(false, getErrorType(error), false); setError(errorMsg); } @@ -201,7 +202,7 @@ export default function RecipesView() { }); } catch (err) { console.error('Failed to delete recipe:', err); - const errorMsg = err instanceof Error ? err.message : 'Failed to delete recipe'; + const errorMsg = errorMessage(err, 'Failed to delete recipe'); trackRecipeDeleted(false, getErrorType(err)); setError(errorMsg); } @@ -341,7 +342,7 @@ export default function RecipesView() { await loadSavedRecipes(); } catch (error) { console.error('Failed to save schedule:', error); - const errorMsg = error instanceof Error ? error.message : 'Failed to save schedule'; + const errorMsg = errorMessage(error, 'Failed to save schedule'); trackRecipeScheduled(false, action, getErrorType(error)); setError(errorMsg); } @@ -369,7 +370,7 @@ export default function RecipesView() { await loadSavedRecipes(); } catch (error) { console.error('Failed to remove schedule:', error); - const errorMsg = error instanceof Error ? error.message : 'Failed to remove schedule'; + const errorMsg = errorMessage(error, 'Failed to remove schedule'); trackRecipeScheduled(false, 'remove', getErrorType(error)); setError(errorMsg); } @@ -409,7 +410,7 @@ export default function RecipesView() { await loadSavedRecipes(); } catch (error) { console.error('Failed to save slash command:', error); - const errorMsg = error instanceof Error ? error.message : 'Failed to save slash command'; + const errorMsg = errorMessage(error, 'Failed to save slash command'); trackRecipeSlashCommandSet(false, action, getErrorType(error)); setError(errorMsg); } @@ -437,7 +438,7 @@ export default function RecipesView() { await loadSavedRecipes(); } catch (error) { console.error('Failed to remove slash command:', error); - const errorMsg = error instanceof Error ? error.message : 'Failed to remove slash command'; + const errorMsg = errorMessage(error, 'Failed to remove slash command'); trackRecipeSlashCommandSet(false, 'remove', getErrorType(error)); setError(errorMsg); } diff --git a/ui/desktop/src/components/schedule/ScheduleDetailView.tsx b/ui/desktop/src/components/schedule/ScheduleDetailView.tsx index cb27887c..a686bbb5 100644 --- a/ui/desktop/src/components/schedule/ScheduleDetailView.tsx +++ b/ui/desktop/src/components/schedule/ScheduleDetailView.tsx @@ -22,6 +22,7 @@ import cronstrue from 'cronstrue'; import { formatToLocalDateWithTimezone } from '../../utils/date'; import { getSession, Session } from '../../api'; import { trackScheduleRunNow, getErrorType } from '../../utils/analytics'; +import { errorMessage } from '../../utils/conversionUtils'; interface ScheduleSessionMeta { id: string; @@ -67,7 +68,7 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN const data = await getScheduleSessions(sId, 20); setSessions(data); } catch (err) { - setSessionsError(err instanceof Error ? err.message : 'Failed to fetch sessions'); + setSessionsError(errorMessage(err, 'Failed to fetch sessions')); } finally { setIsLoadingSessions(false); } @@ -85,7 +86,7 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN setScheduleError('Schedule not found'); } } catch (err) { - setScheduleError(err instanceof Error ? err.message : 'Failed to fetch schedule'); + setScheduleError(errorMessage(err, 'Failed to fetch schedule')); } finally { setIsLoadingSchedule(false); } @@ -112,7 +113,7 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN await fetchSessions(scheduleId); await fetchSchedule(scheduleId); } catch (err) { - const errorMsg = err instanceof Error ? err.message : 'Failed to trigger schedule'; + const errorMsg = errorMessage(err, 'Failed to trigger schedule'); trackScheduleRunNow(false, getErrorType(err)); toastError({ title: 'Run Schedule Error', @@ -136,7 +137,7 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN } await fetchSchedule(scheduleId); } catch (err) { - const errorMsg = err instanceof Error ? err.message : 'Operation failed'; + const errorMsg = errorMessage(err, 'Operation failed'); toastError({ title: 'Pause/Unpause Error', msg: errorMsg, @@ -154,7 +155,7 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN toastSuccess({ title: 'Job Killed', msg: result.message }); await fetchSchedule(scheduleId); } catch (err) { - const errorMsg = err instanceof Error ? err.message : 'Failed to kill job'; + const errorMsg = errorMessage(err, 'Failed to kill job'); toastError({ title: 'Kill Job Error', msg: errorMsg, @@ -181,7 +182,7 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN toastSuccess({ title: 'Job Inspection', msg: 'No detailed information available' }); } } catch (err) { - const errorMsg = err instanceof Error ? err.message : 'Failed to inspect job'; + const errorMsg = errorMessage(err, 'Failed to inspect job'); toastError({ title: 'Inspect Job Error', msg: errorMsg, @@ -200,7 +201,7 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN await fetchSchedule(scheduleId); setIsModalOpen(false); } catch (err) { - const errorMsg = err instanceof Error ? err.message : 'Failed to update schedule'; + const errorMsg = errorMessage(err, 'Failed to update schedule'); toastError({ title: 'Update Schedule Error', msg: errorMsg, @@ -220,7 +221,7 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN }); setSelectedSession(response.data); } catch (err) { - const msg = err instanceof Error ? err.message : 'Failed to load session'; + const msg = errorMessage(err, 'Failed to load session'); setSessionError(msg); toastError({ title: 'Failed to load session', msg }); } finally { diff --git a/ui/desktop/src/components/schedule/SchedulesView.tsx b/ui/desktop/src/components/schedule/SchedulesView.tsx index c7b49b8f..9f96c06d 100644 --- a/ui/desktop/src/components/schedule/SchedulesView.tsx +++ b/ui/desktop/src/components/schedule/SchedulesView.tsx @@ -21,6 +21,7 @@ import ScheduleDetailView from './ScheduleDetailView'; import { toastError, toastSuccess } from '../../toasts'; import cronstrue from 'cronstrue'; import { formatToLocalDateWithTimezone } from '../../utils/date'; +import { errorMessage } from '../../utils/conversionUtils'; import { MainPanelLayout } from '../Layout/MainPanelLayout'; import { ViewOptions } from '../../utils/navigationUtils'; import { trackScheduleCreated, trackScheduleDeleted, getErrorType } from '../../utils/analytics'; @@ -206,9 +207,7 @@ const SchedulesView: React.FC = ({ onClose: _onClose }) => { } catch (error) { console.error('Failed to fetch schedules:', error); setApiError( - error instanceof Error - ? error.message - : 'An unknown error occurred while fetching schedules.' + errorMessage(error, 'An unknown error occurred while fetching schedules.') ); } finally { setIsLoading(false); @@ -270,7 +269,7 @@ const SchedulesView: React.FC = ({ onClose: _onClose }) => { setEditingSchedule(null); } catch (error) { console.error('Failed to save schedule:', error); - const errorMsg = error instanceof Error ? error.message : 'Unknown error saving schedule.'; + const errorMsg = errorMessage(error, 'Unknown error saving schedule.'); setSubmitApiError(errorMsg); if (!editingSchedule) { @@ -295,7 +294,7 @@ const SchedulesView: React.FC = ({ onClose: _onClose }) => { await fetchSchedules(); } catch (error) { console.error(`Failed to delete schedule "${id}":`, error); - const errorMsg = error instanceof Error ? error.message : `Unknown error deleting "${id}".`; + const errorMsg = errorMessage(error, `Unknown error deleting "${id}".`); setApiError(errorMsg); trackScheduleDeleted(false, getErrorType(error)); } finally { @@ -320,7 +319,7 @@ const SchedulesView: React.FC = ({ onClose: _onClose }) => { await fetchSchedules(); } catch (error) { console.error(`Failed to pause schedule "${id}":`, error); - const errorMsg = error instanceof Error ? error.message : `Unknown error pausing "${id}".`; + const errorMsg = errorMessage(error, `Unknown error pausing "${id}".`); setApiError(errorMsg); toastError({ title: 'Pause Schedule Error', @@ -348,7 +347,7 @@ const SchedulesView: React.FC = ({ onClose: _onClose }) => { await fetchSchedules(); } catch (error) { console.error(`Failed to unpause schedule "${id}":`, error); - const errorMsg = error instanceof Error ? error.message : `Unknown error unpausing "${id}".`; + const errorMsg = errorMessage(error, `Unknown error unpausing "${id}".`); setApiError(errorMsg); toastError({ title: 'Unpause Schedule Error', @@ -377,7 +376,7 @@ const SchedulesView: React.FC = ({ onClose: _onClose }) => { } catch (error) { console.error(`Failed to kill running job "${id}":`, error); const errorMsg = - error instanceof Error ? error.message : `Unknown error killing job "${id}".`; + errorMessage(error, `Unknown error killing job "${id}".`); setApiError(errorMsg); toastError({ title: 'Kill Job Error', @@ -415,7 +414,7 @@ const SchedulesView: React.FC = ({ onClose: _onClose }) => { } catch (error) { console.error(`Failed to inspect running job "${id}":`, error); const errorMsg = - error instanceof Error ? error.message : `Unknown error inspecting job "${id}".`; + errorMessage(error, `Unknown error inspecting job "${id}".`); setApiError(errorMsg); toastError({ title: 'Inspect Job Error', diff --git a/ui/desktop/src/components/sessions/SessionHistoryView.tsx b/ui/desktop/src/components/sessions/SessionHistoryView.tsx index 8053a361..65aa2290 100644 --- a/ui/desktop/src/components/sessions/SessionHistoryView.tsx +++ b/ui/desktop/src/components/sessions/SessionHistoryView.tsx @@ -18,6 +18,7 @@ import { MainPanelLayout } from '../Layout/MainPanelLayout'; import { ScrollArea } from '../ui/scroll-area'; import { formatMessageTimestamp } from '../../utils/timeUtils'; import { createSharedSession } from '../../sharedSessions'; +import { errorMessage } from '../../utils/conversionUtils'; import { Dialog, DialogContent, @@ -189,7 +190,7 @@ const SessionHistoryView: React.FC = ({ } catch (error) { console.error('Error sharing session:', error); toast.error( - `Failed to share session: ${error instanceof Error ? error.message : 'Unknown error'}` + `Failed to share session: ${errorMessage(error, 'Unknown error')}` ); } finally { setIsSharing(false); @@ -213,7 +214,7 @@ const SessionHistoryView: React.FC = ({ try { resumeSession(session, setView); } catch (error) { - toast.error(`Could not launch session: ${error instanceof Error ? error.message : error}`); + toast.error(`Could not launch session: ${errorMessage(error)}`); } }; diff --git a/ui/desktop/src/components/sessions/SessionListView.tsx b/ui/desktop/src/components/sessions/SessionListView.tsx index a2178b16..366cab60 100644 --- a/ui/desktop/src/components/sessions/SessionListView.tsx +++ b/ui/desktop/src/components/sessions/SessionListView.tsx @@ -22,6 +22,7 @@ import { SearchView } from '../conversation/SearchView'; import { SearchHighlighter } from '../../utils/searchHighlighter'; import { MainPanelLayout } from '../Layout/MainPanelLayout'; import { groupSessionsByDate, type DateGroup } from '../../utils/dateUtils'; +import { errorMessage } from '../../utils/conversionUtils'; import { Skeleton } from '../ui/skeleton'; import { toast } from 'react-toastify'; import { ConfirmationModal } from '../ui/ConfirmationModal'; @@ -99,9 +100,9 @@ const EditSessionModal = React.memo( toast.success('Session description updated successfully'); }, 300); } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; - console.error('Failed to update session description:', errorMessage); - toast.error(`Failed to update session description: ${errorMessage}`); + const errMsg = errorMessage(error, 'Unknown error occurred'); + console.error('Failed to update session description:', errMsg); + toast.error(`Failed to update session description: ${errMsg}`); setDescription(session.name); } finally { setIsUpdating(false); @@ -450,8 +451,7 @@ const SessionListView: React.FC = React.memo( await loadSessions(); } catch (error) { console.error('Error duplicating session:', error); - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - toast.error(`Failed to duplicate session: ${errorMessage}`); + toast.error(`Failed to duplicate session: ${errorMessage(error, 'Unknown error')}`); } }, [loadSessions] @@ -476,8 +476,7 @@ const SessionListView: React.FC = React.memo( ); } catch (error) { console.error('Error deleting session:', error); - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - toast.error(`Failed to delete session "${sessionName}": ${errorMessage}`); + toast.error(`Failed to delete session "${sessionName}": ${errorMessage(error, 'Unknown error')}`); } await loadSessions(); }, [sessionToDelete, loadSessions]); diff --git a/ui/desktop/src/components/sessions/SessionsInsights.tsx b/ui/desktop/src/components/sessions/SessionsInsights.tsx index a7d76e4b..04a81291 100644 --- a/ui/desktop/src/components/sessions/SessionsInsights.tsx +++ b/ui/desktop/src/components/sessions/SessionsInsights.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import { errorMessage } from '../../utils/conversionUtils'; import { Card, CardContent, CardDescription } from '../ui/card'; import { Greeting } from '../common/Greeting'; import { useNavigate } from 'react-router-dom'; @@ -34,7 +35,7 @@ export function SessionInsights() { setError(null); } catch (error) { console.error('Failed to load insights:', error); - setError(error instanceof Error ? error.message : 'Failed to load insights'); + setError(errorMessage(error, 'Failed to load insights')); setInsights({ totalSessions: 0, totalTokens: 0, diff --git a/ui/desktop/src/components/settings/app/UpdateSection.tsx b/ui/desktop/src/components/settings/app/UpdateSection.tsx index b68aea9e..a2ee60af 100644 --- a/ui/desktop/src/components/settings/app/UpdateSection.tsx +++ b/ui/desktop/src/components/settings/app/UpdateSection.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { Button } from '../../ui/button'; import { Loader2, Download, CheckCircle, AlertCircle } from 'lucide-react'; +import { errorMessage } from '../../../utils/conversionUtils'; type UpdateStatus = | 'idle' @@ -154,7 +155,7 @@ export default function UpdateSection() { console.error('Error checking for updates:', error); setUpdateInfo((prev) => ({ ...prev, - error: error instanceof Error ? error.message : 'Failed to check for updates', + error: errorMessage(error, 'Failed to check for updates'), })); setUpdateStatus('error'); setTimeout(() => setUpdateStatus('idle'), 5000); diff --git a/ui/desktop/src/components/settings/config/ConfigSettings.tsx b/ui/desktop/src/components/settings/config/ConfigSettings.tsx index 4085f444..2a701c7e 100644 --- a/ui/desktop/src/components/settings/config/ConfigSettings.tsx +++ b/ui/desktop/src/components/settings/config/ConfigSettings.tsx @@ -17,6 +17,7 @@ import { DialogTitle, DialogTrigger, } from '../../ui/dialog'; +import { errorMessage } from '../../../utils/conversionUtils'; export default function ConfigSettings() { const { config, upsert } = useConfig(); @@ -84,7 +85,7 @@ export default function ConfigSettings() { toastError({ title: 'Save Failed', msg: `Failed to save "${getUiNames(key)}"`, - traceback: error instanceof Error ? error.message : String(error), + traceback: errorMessage(error), }); } finally { setSaving(null); diff --git a/ui/desktop/src/components/settings/models/modelInterface.ts b/ui/desktop/src/components/settings/models/modelInterface.ts index 5af97bba..f5fb7375 100644 --- a/ui/desktop/src/components/settings/models/modelInterface.ts +++ b/ui/desktop/src/components/settings/models/modelInterface.ts @@ -1,4 +1,5 @@ import { ProviderDetails, getProviderModels } from '../../../api'; +import { errorMessage as getErrorMessage } from '../../../utils/conversionUtils'; export default interface Model { id?: number; // Make `id` optional to allow user-defined models @@ -60,7 +61,8 @@ export async function fetchModelsForProviders( const models = response.data || []; return { provider: p, models, error: null }; } catch (e: unknown) { - const errorMessage = `Failed to fetch models for ${p.name}${e instanceof Error ? `: ${e.message}` : ''}`; + const errMsg = getErrorMessage(e); + const errorMessage = `Failed to fetch models for ${p.name}${errMsg ? `: ${errMsg}` : ''}`; return { provider: p, models: null, diff --git a/ui/desktop/src/hooks/useFileDrop.ts b/ui/desktop/src/hooks/useFileDrop.ts index 40de5e01..6e218f41 100644 --- a/ui/desktop/src/hooks/useFileDrop.ts +++ b/ui/desktop/src/hooks/useFileDrop.ts @@ -1,5 +1,5 @@ import { useCallback, useState, useRef, useEffect } from 'react'; -import { compressImageDataUrl } from '../utils/conversionUtils'; +import { compressImageDataUrl, errorMessage } from '../utils/conversionUtils'; export interface DroppedFile { id: string; @@ -66,7 +66,7 @@ export const useFileDrop = () => { type: file.type, isImage: false, isLoading: false, - error: `Failed to get file path: ${error instanceof Error ? error.message : 'Unknown error'}`, + error: `Failed to get file path: ${errorMessage(error, 'Unknown error')}`, }; } diff --git a/ui/desktop/src/hooks/useWhisper.ts b/ui/desktop/src/hooks/useWhisper.ts index caf8a1a3..09c712c3 100644 --- a/ui/desktop/src/hooks/useWhisper.ts +++ b/ui/desktop/src/hooks/useWhisper.ts @@ -3,7 +3,7 @@ import { useConfig } from '../components/ConfigContext'; import { getApiUrl } from '../config'; import { useDictationSettings } from './useDictationSettings'; import { DICTATION_PROVIDER_OPENAI, DICTATION_PROVIDER_ELEVENLABS } from './dictationConstants'; -import { safeJsonParse } from '../utils/conversionUtils'; +import { safeJsonParse, errorMessage } from '../utils/conversionUtils'; interface UseWhisperOptions { onTranscription?: (text: string) => void; @@ -353,8 +353,7 @@ export const useWhisper = ({ onTranscription, onError, onSizeWarning }: UseWhisp setIsRecording(true); } catch (startError) { console.error('Error calling mediaRecorder.start():', startError); - const errorMessage = startError instanceof Error ? startError.message : String(startError); - throw new Error(`Failed to start recording: ${errorMessage}`); + throw new Error(`Failed to start recording: ${errorMessage(startError)}`); } } catch (error) { console.error('Error starting recording:', error); diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 0da4edeb..b4b55acd 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -28,7 +28,7 @@ import { expandTilde } from './utils/pathUtils'; import log from './utils/logger'; import { ensureWinShims } from './utils/winShims'; import { addRecentDir, loadRecentDirs } from './utils/recentDirs'; -import { formatAppName } from './utils/conversionUtils'; +import { formatAppName, errorMessage } from './utils/conversionUtils'; import type { Settings } from './utils/settings'; import { defaultKeyboardShortcuts, getKeyboardShortcuts } from './utils/settings'; import * as crypto from 'crypto'; @@ -1073,8 +1073,7 @@ function parseRecipeDeeplink(url: string): RecipeDeeplinkData | undefined { try { recipeDeeplink = decodeURIComponent(recipeDeeplinkTmp); } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error('[Main] parseRecipeDeeplink - Failed to decode:', errorMessage); + console.error('[Main] parseRecipeDeeplink - Failed to decode:', errorMessage(error)); return undefined; } } diff --git a/ui/desktop/src/sessionLinks.ts b/ui/desktop/src/sessionLinks.ts index fc30dab0..851bbba3 100644 --- a/ui/desktop/src/sessionLinks.ts +++ b/ui/desktop/src/sessionLinks.ts @@ -1,5 +1,6 @@ import { fetchSharedSessionDetails, SharedSessionDetails } from './sharedSessions'; import { View, ViewOptions } from './utils/navigationUtils'; +import { errorMessage } from './utils/conversionUtils'; /** * Handles opening a shared session from a deep link @@ -61,13 +62,14 @@ export async function openSharedSessionFromDeepLink( return sessionDetails; } catch (error) { - const errorMessage = `Failed to open shared session: ${error instanceof Error ? error.message : 'Unknown error'}`; - console.error(errorMessage); + const errMsg = errorMessage(error, 'Unknown error'); + const fullErrorMessage = `Failed to open shared session: ${errMsg}`; + console.error(fullErrorMessage); // Navigate to the shared session view with the error instead of throwing setView('sharedSession', { sessionDetails: null, - error: error instanceof Error ? error.message : 'Unknown error', + error: errMsg, shareToken: url.replace('goose://sessions/', ''), baseUrl, }); diff --git a/ui/desktop/src/utils/autoUpdater.ts b/ui/desktop/src/utils/autoUpdater.ts index 2694ed64..4d428c58 100644 --- a/ui/desktop/src/utils/autoUpdater.ts +++ b/ui/desktop/src/utils/autoUpdater.ts @@ -16,6 +16,7 @@ import * as fs from 'fs/promises'; import log from './logger'; import { githubUpdater } from './githubUpdater'; import { loadRecentDirs } from './recentDirs'; +import { errorMessage } from './conversionUtils'; import { trackUpdateCheckStarted, trackUpdateCheckCompleted, @@ -99,7 +100,7 @@ export function registerUpdateIpcHandlers() { log.error(`=== MANUAL UPDATE CHECK FAILED after ${duration}ms ===`); log.error('Error checking for updates:', error); log.error('Manual check error details:', { - message: error instanceof Error ? error.message : 'Unknown error', + message: errorMessage(error, 'Unknown error'), stack: error instanceof Error ? error.stack : 'No stack', name: error instanceof Error ? error.name : 'Unknown', code: @@ -189,12 +190,12 @@ export function registerUpdateIpcHandlers() { trackUpdateCheckCompleted('error', currentVersion, { usingFallback: false, - errorType: error instanceof Error ? error.message : 'unknown', + errorType: errorMessage(error, 'unknown'), }); return { updateInfo: null, - error: error instanceof Error ? error.message : 'Unknown error', + error: errorMessage(error, 'Unknown error'), }; } }); @@ -250,11 +251,11 @@ export function registerUpdateIpcHandlers() { false, version, method, - error instanceof Error ? error.message : 'unknown' + errorMessage(error, 'unknown') ); return { success: false, - error: error instanceof Error ? error.message : 'Unknown error', + error: errorMessage(error, 'Unknown error'), }; } }); @@ -708,7 +709,7 @@ async function githubAutoDownload( false, latestVersion, 'github-fallback', - downloadError instanceof Error ? downloadError.message : 'unknown' + errorMessage(downloadError, 'unknown') ); log.error( `Error during GitHub auto-download${contextLabel ? ` (${contextLabel})` : ''}:`, diff --git a/ui/desktop/src/utils/githubUpdater.ts b/ui/desktop/src/utils/githubUpdater.ts index 4a87b1ff..cd2e1887 100644 --- a/ui/desktop/src/utils/githubUpdater.ts +++ b/ui/desktop/src/utils/githubUpdater.ts @@ -4,7 +4,7 @@ import * as fs from 'fs/promises'; import * as path from 'path'; import * as os from 'os'; import log from './logger'; -import { safeJsonParse } from './conversionUtils'; +import { safeJsonParse, errorMessage } from './conversionUtils'; interface GitHubRelease { tag_name: string; @@ -142,7 +142,7 @@ export class GitHubUpdater { } catch (error) { log.error('GitHubUpdater: Error checking for updates:', error); log.error('GitHubUpdater: Error details:', { - message: error instanceof Error ? error.message : 'Unknown error', + message: errorMessage(error, 'Unknown error'), stack: error instanceof Error ? error.stack : 'No stack', name: error instanceof Error ? error.name : 'Unknown', code: @@ -152,7 +152,7 @@ export class GitHubUpdater { }); return { updateAvailable: false, - error: error instanceof Error ? error.message : 'Unknown error', + error: errorMessage(error, 'Unknown error'), }; } } @@ -271,13 +271,13 @@ export class GitHubUpdater { log.error(`=== GitHubUpdater: DOWNLOAD FAILED after ${duration}ms ===`); log.error('GitHubUpdater: Error downloading update:', error); log.error('GitHubUpdater: Download error details:', { - message: error instanceof Error ? error.message : 'Unknown error', + message: errorMessage(error, 'Unknown error'), stack: error instanceof Error ? error.stack : 'No stack', name: error instanceof Error ? error.name : 'Unknown', }); return { success: false, - error: error instanceof Error ? error.message : 'Unknown error', + error: errorMessage(error, 'Unknown error'), }; } } diff --git a/ui/desktop/src/utils/ollamaDetection.ts b/ui/desktop/src/utils/ollamaDetection.ts index bbe13e4e..f639d902 100644 --- a/ui/desktop/src/utils/ollamaDetection.ts +++ b/ui/desktop/src/utils/ollamaDetection.ts @@ -1,3 +1,5 @@ +import { errorMessage } from './conversionUtils'; + const DEFAULT_OLLAMA_HOST = 'http://127.0.0.1:11434'; const OLLAMA_DOWNLOAD_URL = 'https://ollama.com/download'; const PREFERRED_MODEL = 'gpt-oss:20b'; @@ -52,7 +54,7 @@ export async function checkOllamaStatus(): Promise { return { isRunning: false, host: DEFAULT_OLLAMA_HOST, - error: error instanceof Error ? error.message : 'Unknown error', + error: errorMessage(error, 'Unknown error'), }; } } diff --git a/ui/desktop/src/utils/sessionCache.ts b/ui/desktop/src/utils/sessionCache.ts index b4a9ef71..1daaf383 100644 --- a/ui/desktop/src/utils/sessionCache.ts +++ b/ui/desktop/src/utils/sessionCache.ts @@ -1,5 +1,6 @@ import { Session } from '../api'; import { getApiUrl } from '../config'; +import { errorMessage } from './conversionUtils'; /** * In-memory cache for session data @@ -57,10 +58,7 @@ export async function loadSession(sessionId: string, forceRefresh = false): Prom return session; } catch (error) { - if (error instanceof Error) { - throw new Error(`Error loading session ${sessionId}: ${error.message}`); - } - throw new Error(`Error loading session ${sessionId}: Unknown error`); + throw new Error(`Error loading session ${sessionId}: ${errorMessage(error, 'Unknown error')}`); } finally { inFlightRequests.delete(sessionId); }