diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 36a494a73f24..fa3121b6e3b0 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -8,6 +8,7 @@ import { ToastContainer } from 'react-toastify'; import { toastService } from './toasts'; import { extractExtensionName } from './components/settings/extensions/utils'; import { GoosehintsModal } from './components/GoosehintsModal'; +import { type ExtensionConfig } from './extensions'; import ChatView from './components/ChatView'; import SuspenseLoader from './suspense-loader'; @@ -46,10 +47,28 @@ export type View = | 'recipeEditor' | 'permission'; -export type ViewOptions = - | SettingsViewOptions - | { resumedSession?: SessionDetails } - | Record; +export type ViewOptions = { + // Settings view options + extensionId?: string; + showEnvVars?: boolean; + deepLinkConfig?: ExtensionConfig; + + // Session view options + resumedSession?: SessionDetails; + sessionDetails?: SessionDetails; + error?: string; + shareToken?: string; + baseUrl?: string; + + // Recipe editor options + config?: unknown; + + // Permission view options + parentView?: View; + + // Generic options + [key: string]: unknown; +}; export type ViewConfig = { view: View; @@ -164,7 +183,7 @@ export default function App() { if (provider && model) { setView('chat'); try { - await initializeSystem(provider, model, { + await initializeSystem(provider as string, model as string, { getExtensions, addExtension, }); @@ -289,7 +308,7 @@ export default function App() { }; setView(viewFromUrl, initialViewOptions); } else { - setView(viewFromUrl); + setView(viewFromUrl as View); } } window.electron.on('set-view', handleSetView); diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx index 8767f9a5b077..77f4a2bb63f0 100644 --- a/ui/desktop/src/components/ChatInput.tsx +++ b/ui/desktop/src/components/ChatInput.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState, useEffect, useCallback } from 'react'; +import React, { useRef, useState, useEffect, useMemo } from 'react'; import { Button } from './ui/button'; import type { View } from '../App'; import Stop from './ui/Stop'; @@ -148,21 +148,20 @@ export default function ChatInput({ }, [droppedFiles, processedFilePaths, displayValue]); // Debounced function to update actual value - const debouncedSetValue = useCallback((val: string) => { - debounce((value: string) => { + const debouncedSetValue = useMemo( + () => debounce((value: string) => { setValue(value); - }, 150)(val); - }, []); + }, 150), + [setValue] + ); // Debounced autosize function - const debouncedAutosize = useCallback( - (textArea: HTMLTextAreaElement) => { - debounce((element: HTMLTextAreaElement) => { - element.style.height = '0px'; // Reset height - const scrollHeight = element.scrollHeight; - element.style.height = Math.min(scrollHeight, maxHeight) + 'px'; - }, 150)(textArea); - }, + const debouncedAutosize = useMemo( + () => debounce((element: HTMLTextAreaElement) => { + element.style.height = '0px'; // Reset height + const scrollHeight = element.scrollHeight; + element.style.height = Math.min(scrollHeight, maxHeight) + 'px'; + }, 150), [maxHeight] ); diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index ca0fc0b35279..0e54d0a1911e 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -272,7 +272,7 @@ function ChatContent({ // Update chat messages when they change and save to sessionStorage useEffect(() => { - setChat((prevChat) => { + setChat((prevChat: ChatType) => { const updatedChat = { ...prevChat, messages }; return updatedChat; }); diff --git a/ui/desktop/src/components/ErrorBoundary.tsx b/ui/desktop/src/components/ErrorBoundary.tsx index eec01f6fe6d3..1c29d2a6b521 100644 --- a/ui/desktop/src/components/ErrorBoundary.tsx +++ b/ui/desktop/src/components/ErrorBoundary.tsx @@ -14,7 +14,7 @@ window.addEventListener('error', (event) => { ); }); -export function ErrorUI({ error }) { +export function ErrorUI({ error }: { error: Error }) { return (
diff --git a/ui/desktop/src/components/FlappyGoose.tsx b/ui/desktop/src/components/FlappyGoose.tsx index 3cd8b589bc23..5cd9cb35534a 100644 --- a/ui/desktop/src/components/FlappyGoose.tsx +++ b/ui/desktop/src/components/FlappyGoose.tsx @@ -216,7 +216,7 @@ const FlappyGoose: React.FC = ({ onClose }) => { useEffect(() => { const frames = [svg1, svg7]; frames.forEach((src, index) => { - const img = new Image(); + const img = new Image() as HTMLImageElement; img.src = src; img.onload = () => { framesLoaded.current += 1; diff --git a/ui/desktop/src/components/GooseLogo.tsx b/ui/desktop/src/components/GooseLogo.tsx index 1df25c62f547..4d8ea402c59a 100644 --- a/ui/desktop/src/components/GooseLogo.tsx +++ b/ui/desktop/src/components/GooseLogo.tsx @@ -1,7 +1,12 @@ -import React from 'react'; import { Goose, Rain } from './icons/Goose'; -export default function GooseLogo({ className = '', size = 'default', hover = true }) { +interface GooseLogoProps { + className?: string; + size?: 'default' | 'small'; + hover?: boolean; +} + +export default function GooseLogo({ className = '', size = 'default', hover = true }: GooseLogoProps) { const sizes = { default: { frame: 'w-16 h-16', @@ -13,15 +18,18 @@ export default function GooseLogo({ className = '', size = 'default', hover = tr rain: 'w-[150px] h-[150px]', goose: 'w-8 h-8', }, - }; + } as const; + + const currentSize = sizes[size]; + return (
- +
); } diff --git a/ui/desktop/src/components/GooseMessage.tsx b/ui/desktop/src/components/GooseMessage.tsx index 79f62b976267..6066386b3945 100644 --- a/ui/desktop/src/components/GooseMessage.tsx +++ b/ui/desktop/src/components/GooseMessage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import LinkPreview from './LinkPreview'; import ImagePreview from './ImagePreview'; import GooseResponseForm from './GooseResponseForm'; diff --git a/ui/desktop/src/components/GoosehintsModal.tsx b/ui/desktop/src/components/GoosehintsModal.tsx index 8f78e886c879..19624efb37d3 100644 --- a/ui/desktop/src/components/GoosehintsModal.tsx +++ b/ui/desktop/src/components/GoosehintsModal.tsx @@ -3,7 +3,7 @@ import { Card } from './ui/card'; import { Button } from './ui/button'; import { Check } from './icons'; -const Modal = ({ children }) => ( +const Modal = ({ children }: { children: React.ReactNode }) => (
@@ -48,13 +48,13 @@ const ModalHelpText = () => (
); -const ModalError = ({ error }) => ( +const ModalError = ({ error }: { error: Error }) => (
Error reading .goosehints file: {JSON.stringify(error)}
); -const ModalFileInfo = ({ filePath, found }) => ( +const ModalFileInfo = ({ filePath, found }: { filePath: string; found: boolean }) => (
{found ? (
@@ -66,7 +66,7 @@ const ModalFileInfo = ({ filePath, found }) => (
); -const ModalButtons = ({ onSubmit, onCancel }) => ( +const ModalButtons = ({ onSubmit, onCancel }: { onSubmit: () => void; onCancel: () => void }) => (
); -const getGoosehintsFile = async (filePath) => await window.electron.readFile(filePath); +const getGoosehintsFile = async (filePath: string) => await window.electron.readFile(filePath); type GoosehintsModalProps = { directory: string; diff --git a/ui/desktop/src/components/ImagePreview.tsx b/ui/desktop/src/components/ImagePreview.tsx index 7e6d66f5c1b9..29e0aff33c92 100644 --- a/ui/desktop/src/components/ImagePreview.tsx +++ b/ui/desktop/src/components/ImagePreview.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; interface ImagePreviewProps { src: string; diff --git a/ui/desktop/src/components/LayingEggLoader.tsx b/ui/desktop/src/components/LayingEggLoader.tsx index 3d96947f49c4..faa4f80604a1 100644 --- a/ui/desktop/src/components/LayingEggLoader.tsx +++ b/ui/desktop/src/components/LayingEggLoader.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { Geese } from './icons/Geese'; export default function LayingEggLoader() { diff --git a/ui/desktop/src/components/LinkPreview.tsx b/ui/desktop/src/components/LinkPreview.tsx index 71b33417c355..f4fc835b4651 100644 --- a/ui/desktop/src/components/LinkPreview.tsx +++ b/ui/desktop/src/components/LinkPreview.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { Card } from './ui/card'; interface Metadata { @@ -85,10 +85,11 @@ export default function LinkPreview({ url }: LinkPreviewProps) { if (mounted) { setMetadata(data); } - } catch (error) { + } catch (err) { if (mounted) { - console.error('❌ Failed to fetch metadata:', error); - setError(error.message || 'Failed to fetch metadata'); + console.error('❌ Failed to fetch metadata:', err); + const errorMessage = err instanceof Error ? err.message : 'Failed to fetch metadata'; + setError(errorMessage); } } finally { if (mounted) { diff --git a/ui/desktop/src/components/LoadingGoose.tsx b/ui/desktop/src/components/LoadingGoose.tsx index 38677d18f5ab..e3c623f251f3 100644 --- a/ui/desktop/src/components/LoadingGoose.tsx +++ b/ui/desktop/src/components/LoadingGoose.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import GooseLogo from './GooseLogo'; const LoadingGoose = () => { diff --git a/ui/desktop/src/components/ProviderGrid.tsx b/ui/desktop/src/components/ProviderGrid.tsx index 9ad66b9d84b0..1e3d6721478d 100644 --- a/ui/desktop/src/components/ProviderGrid.tsx +++ b/ui/desktop/src/components/ProviderGrid.tsx @@ -43,7 +43,7 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) { }); }, [activeKeys]); - const handleConfigure = async (provider) => { + const handleConfigure = async (provider: { id: string; name: string; isConfigured: boolean; description: string }) => { const providerId = provider.id.toLowerCase(); const modelName = getDefaultModel(providerId); @@ -63,7 +63,7 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) { onSubmit?.(); }; - const handleAddKeys = (provider) => { + const handleAddKeys = (provider: { id: string; name: string; isConfigured: boolean; description: string }) => { setSelectedId(provider.id); setShowSetupModal(true); }; @@ -74,7 +74,7 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) { const provider = providers.find((p) => p.id === selectedId)?.name; if (!provider) return; - const requiredKeys = required_keys[provider]; + const requiredKeys = required_keys[provider as keyof typeof required_keys]; if (!requiredKeys || requiredKeys.length === 0) { console.error(`No keys found for provider ${provider}`); return; @@ -145,12 +145,13 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) { setShowSetupModal(false); setSelectedId(null); - } catch (error) { - console.error('Error handling modal submit:', error); + } catch (err) { + console.error('Error handling modal submit:', err); + const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; toastError({ title: provider, msg: `Failed to ${providers.find((p) => p.id === selectedId)?.isConfigured ? 'update' : 'add'} configuration`, - traceback: error.message, + traceback: errorMessage, }); } }; diff --git a/ui/desktop/src/components/RecipeEditor.tsx b/ui/desktop/src/components/RecipeEditor.tsx index efbb85d2f7eb..a621d32cb24d 100644 --- a/ui/desktop/src/components/RecipeEditor.tsx +++ b/ui/desktop/src/components/RecipeEditor.tsx @@ -54,7 +54,7 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { } } // Fall back to config if available, using extension names - const exts = []; + const exts: string[] = []; return exts; }); // Section visibility state @@ -125,7 +125,10 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { delete cleanExtension.enabled; // Remove legacy envs which could potentially include secrets // env_keys will work but rely on the end user having setup those keys themselves - delete cleanExtension.envs; + if ('envs' in cleanExtension) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete (cleanExtension as any).envs; + } return cleanExtension; }) .filter(Boolean) as FullExtensionConfig[], diff --git a/ui/desktop/src/components/ToolCallArguments.tsx b/ui/desktop/src/components/ToolCallArguments.tsx index 581f1580a1e9..b2e31a1f09bc 100644 --- a/ui/desktop/src/components/ToolCallArguments.tsx +++ b/ui/desktop/src/components/ToolCallArguments.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import MarkdownContent from './MarkdownContent'; import Expand from './ui/Expand'; diff --git a/ui/desktop/src/components/UserMessage.tsx b/ui/desktop/src/components/UserMessage.tsx index 4fad212f24ea..a871b5b1ad14 100644 --- a/ui/desktop/src/components/UserMessage.tsx +++ b/ui/desktop/src/components/UserMessage.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useMemo } from 'react'; +import { useRef, useMemo } from 'react'; import LinkPreview from './LinkPreview'; import ImagePreview from './ImagePreview'; import { extractUrls } from '../utils/urlUtils'; diff --git a/ui/desktop/src/components/WelcomeView.tsx b/ui/desktop/src/components/WelcomeView.tsx index 5003d7403a5e..2e5779e350c0 100644 --- a/ui/desktop/src/components/WelcomeView.tsx +++ b/ui/desktop/src/components/WelcomeView.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { ProviderGrid } from './ProviderGrid'; import { ScrollArea } from './ui/scroll-area'; import { Button } from './ui/button'; diff --git a/ui/desktop/src/components/bottom_menu/BottomMenuModeSelection.tsx b/ui/desktop/src/components/bottom_menu/BottomMenuModeSelection.tsx index 092a6c0be348..eb9418dc0efa 100644 --- a/ui/desktop/src/components/bottom_menu/BottomMenuModeSelection.tsx +++ b/ui/desktop/src/components/bottom_menu/BottomMenuModeSelection.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState, useCallback } from 'react'; +import { useEffect, useRef, useState, useCallback } from 'react'; import { all_goose_modes, ModeSelectionItem } from '../settings_v2/mode/ModeSelectionItem'; import { useConfig } from '../ConfigContext'; import { View, ViewOptions } from '../../App'; diff --git a/ui/desktop/src/components/conversation/SearchView.tsx b/ui/desktop/src/components/conversation/SearchView.tsx index 4f331dab37f1..5024a3328bf5 100644 --- a/ui/desktop/src/components/conversation/SearchView.tsx +++ b/ui/desktop/src/components/conversation/SearchView.tsx @@ -231,23 +231,19 @@ export const SearchView: React.FC> = ({ highlighterRef.current = null; } - // Cancel any pending highlight operations - debouncedHighlight.cancel?.(); - // Clear search when closing onSearch?.('', false); - }, [debouncedHighlight, onSearch]); + }, [onSearch]); - // Clean up highlighter and debounced functions on unmount + // Clean up highlighter on unmount useEffect(() => { return () => { if (highlighterRef.current) { highlighterRef.current.destroy(); highlighterRef.current = null; } - debouncedHighlight.cancel?.(); }; - }, [debouncedHighlight]); + }, []); // Listen for keyboard events useEffect(() => { diff --git a/ui/desktop/src/components/icons/ChevronDown.tsx b/ui/desktop/src/components/icons/ChevronDown.tsx index b69908d6ca9e..b730afa75295 100644 --- a/ui/desktop/src/components/icons/ChevronDown.tsx +++ b/ui/desktop/src/components/icons/ChevronDown.tsx @@ -1,5 +1,5 @@ -export default function ChevronDown({ className }) { +export default function ChevronDown({ className }: { className?: string }) { return ( = ({ setView }) => { // Keep the selected session null if there's an error setSelectedSession(null); + const errorMessage = err instanceof Error ? err.message : String(err); toastError({ title: 'Failed to load session. The file may be corrupted.', msg: 'Please try again later.', - traceback: err, + traceback: errorMessage, }); } finally { setIsLoadingSession(false); diff --git a/ui/desktop/src/components/settings/OllamaBattleGame.tsx b/ui/desktop/src/components/settings/OllamaBattleGame.tsx index ae6a5f0d094b..b282640d8e01 100644 --- a/ui/desktop/src/components/settings/OllamaBattleGame.tsx +++ b/ui/desktop/src/components/settings/OllamaBattleGame.tsx @@ -24,6 +24,7 @@ interface OllamaBattleGameProps { export function OllamaBattleGame({ onComplete, requiredKeys: _ }: OllamaBattleGameProps) { // Use Audio element type for audioRef + // eslint-disable-next-line no-undef const audioRef = useRef(null); const [isMuted, setIsMuted] = useState(false); diff --git a/ui/desktop/src/components/settings/api_keys/ActiveKeysContext.tsx b/ui/desktop/src/components/settings/api_keys/ActiveKeysContext.tsx index 52f7b6ae7158..7533352de426 100644 --- a/ui/desktop/src/components/settings/api_keys/ActiveKeysContext.tsx +++ b/ui/desktop/src/components/settings/api_keys/ActiveKeysContext.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react'; +import { createContext, useContext, useState, ReactNode, useEffect } from 'react'; import { getActiveProviders } from './utils'; import SuspenseLoader from '../../../suspense-loader'; diff --git a/ui/desktop/src/components/settings/api_keys/utils.tsx b/ui/desktop/src/components/settings/api_keys/utils.tsx index 994a547bb1eb..3cfb5051aa66 100644 --- a/ui/desktop/src/components/settings/api_keys/utils.tsx +++ b/ui/desktop/src/components/settings/api_keys/utils.tsx @@ -56,16 +56,16 @@ export async function getActiveProviders(): Promise { // For providers with multiple keys or keys without defaults: // Check if all required keys without defaults are set const requiredNonDefaultKeys = providerRequiredKeys.filter( - (key) => !(key in default_key_value) + (key: string) => !(key in default_key_value) ); // If there are no non-default keys, this provider needs at least one key explicitly set if (requiredNonDefaultKeys.length === 0) { - return providerRequiredKeys.some((key) => configStatus[key]?.is_set === true); + return providerRequiredKeys.some((key: string) => configStatus[key]?.is_set === true); } // Otherwise, all non-default keys must be set - return requiredNonDefaultKeys.every((key) => configStatus[key]?.is_set === true); + return requiredNonDefaultKeys.every((key: string) => configStatus[key]?.is_set === true); }) .map((provider) => provider.name || 'Unknown Provider'); @@ -96,14 +96,14 @@ export async function getConfigSettings(): Promise = {}; providers.forEach((provider) => { - const providerRequiredKeys = required_keys[provider.name] || []; + const providerRequiredKeys = required_keys[provider.name as keyof typeof required_keys] || []; data[provider.name] = { name: provider.name, supported: true, description: provider.metadata.description, models: provider.metadata.models, - config_status: providerRequiredKeys.reduce>((acc, key) => { + config_status: providerRequiredKeys.reduce>((acc: Record, key: string) => { acc[key] = { key, is_set: provider.is_configured, diff --git a/ui/desktop/src/components/settings/basic/ModeSelection.tsx b/ui/desktop/src/components/settings/basic/ModeSelection.tsx index 963074966e4f..b38aec1266af 100644 --- a/ui/desktop/src/components/settings/basic/ModeSelection.tsx +++ b/ui/desktop/src/components/settings/basic/ModeSelection.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useCallback } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import { all_goose_modes, filterGooseModes, ModeSelectionItem } from './ModeSelectionItem'; import { useConfig } from '../../ConfigContext'; diff --git a/ui/desktop/src/components/settings/basic/ModeSelectionItem.tsx b/ui/desktop/src/components/settings/basic/ModeSelectionItem.tsx index f025b80a62fe..d52ca600026a 100644 --- a/ui/desktop/src/components/settings/basic/ModeSelectionItem.tsx +++ b/ui/desktop/src/components/settings/basic/ModeSelectionItem.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { Gear } from '../../icons'; import { ConfigureApproveMode } from './ConfigureApproveMode'; diff --git a/ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx b/ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx index d41b0640c6da..e8e3494a3fd3 100644 --- a/ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx +++ b/ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx @@ -74,12 +74,13 @@ export function ConfigureBuiltInExtensionModal({ }); onSubmit(); onClose(); - } catch (error) { - console.error('Error configuring extension:', error); + } catch (err) { + console.error('Error configuring extension:', err); + const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; toastError({ title: extension.name, msg: `Failed to configure the extension`, - traceback: error.message, + traceback: errorMessage, }); } finally { setIsSubmitting(false); diff --git a/ui/desktop/src/components/settings/extensions/ConfigureExtensionModal.tsx b/ui/desktop/src/components/settings/extensions/ConfigureExtensionModal.tsx index 82dcf8ba7638..0fbea05863d4 100644 --- a/ui/desktop/src/components/settings/extensions/ConfigureExtensionModal.tsx +++ b/ui/desktop/src/components/settings/extensions/ConfigureExtensionModal.tsx @@ -76,12 +76,13 @@ export function ConfigureExtensionModal({ }); onSubmit(); onClose(); - } catch (error) { - console.error('Error configuring extension:', error); + } catch (err) { + console.error('Error configuring extension:', err); + const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; toastError({ title: extension.name, msg: `Failed to configure extension`, - traceback: error.message, + traceback: errorMessage, }); } finally { setIsSubmitting(false); diff --git a/ui/desktop/src/components/settings/extensions/ManualExtensionModal.tsx b/ui/desktop/src/components/settings/extensions/ManualExtensionModal.tsx index db03a5a3abc7..c3b5a158e05c 100644 --- a/ui/desktop/src/components/settings/extensions/ManualExtensionModal.tsx +++ b/ui/desktop/src/components/settings/extensions/ManualExtensionModal.tsx @@ -142,7 +142,7 @@ export function ManualExtensionModal({ isOpen, onClose, onSubmit }: ManualExtens