diff --git a/ui/desktop/package.json b/ui/desktop/package.json index 339fdfe32476..726385773da0 100644 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -26,7 +26,7 @@ "test-e2e:report": "playwright show-report", "test-e2e:single": "npm run generate-api && playwright test -g", "lint": "eslint \"src/**/*.{ts,tsx}\" --fix --no-warn-ignored", - "lint:check": "eslint \"src/**/*.{ts,tsx}\" --max-warnings 0 --no-warn-ignored", + "lint:check": "npm run typecheck && eslint \"src/**/*.{ts,tsx}\" --max-warnings 0 --no-warn-ignored", "format": "prettier --write \"src/**/*.{ts,tsx,css,json}\"", "format:check": "prettier --check \"src/**/*.{ts,tsx,css,json}\"", "prepare": "cd ../.. && husky install", @@ -73,6 +73,7 @@ "license": "Apache-2.0", "lint-staged": { "src/**/*.{ts,tsx}": [ + "bash -c 'npm run typecheck'", "eslint --fix --max-warnings 0 --no-warn-ignored", "prettier --write" ], diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index fa3121b6e3b0..e4418b1caaef 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { IpcRendererEvent } from 'electron'; -import { openSharedSessionFromDeepLink } from './sessionLinks'; +import { openSharedSessionFromDeepLink, type SessionLinksViewOptions } from './sessionLinks'; +import { type SharedSessionDetails } from './sharedSessions'; import { initializeSystem } from './utils/providerUtils'; import { ErrorUI } from './components/ErrorBoundary'; import { ConfirmationModal } from './components/ui/ConfirmationModal'; @@ -9,6 +10,7 @@ import { toastService } from './toasts'; import { extractExtensionName } from './components/settings/extensions/utils'; import { GoosehintsModal } from './components/GoosehintsModal'; import { type ExtensionConfig } from './extensions'; +import { type Recipe } from './recipe'; import ChatView from './components/ChatView'; import SuspenseLoader from './suspense-loader'; @@ -52,20 +54,20 @@ export type ViewOptions = { extensionId?: string; showEnvVars?: boolean; deepLinkConfig?: ExtensionConfig; - - // Session view options + + // 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; }; @@ -237,12 +239,18 @@ export default function App() { }, []); useEffect(() => { - const handleOpenSharedSession = async (_event: IpcRendererEvent, link: string) => { + const handleOpenSharedSession = async (_event: IpcRendererEvent, ...args: unknown[]) => { + const link = args[0] as string; window.electron.logInfo(`Opening shared session from deep link ${link}`); setIsLoadingSharedSession(true); setSharedSessionError(null); try { - await openSharedSessionFromDeepLink(link, setView); + await openSharedSessionFromDeepLink( + link, + (view: View, options?: SessionLinksViewOptions) => { + setView(view, options as ViewOptions); + } + ); } catch (error) { console.error('Unexpected error opening shared session:', error); setView('sessions'); @@ -279,7 +287,8 @@ export default function App() { useEffect(() => { console.log('Setting up fatal error handler'); - const handleFatalError = (_event: IpcRendererEvent, errorMessage: string) => { + const handleFatalError = (_event: IpcRendererEvent, ...args: unknown[]) => { + const errorMessage = args[0] as string; console.error('Encountered a fatal error: ', errorMessage); console.error('Current view:', view); console.error('Is loading session:', isLoadingSession); @@ -293,7 +302,8 @@ export default function App() { useEffect(() => { console.log('Setting up view change handler'); - const handleSetView = (_event: IpcRendererEvent, newView: View) => { + const handleSetView = (_event: IpcRendererEvent, ...args: unknown[]) => { + const newView = args[0] as View; console.log(`Received view change request to: ${newView}`); setView(newView); }; @@ -328,7 +338,8 @@ export default function App() { useEffect(() => { console.log('Setting up extension handler'); - const handleAddExtension = async (_event: IpcRendererEvent, link: string) => { + const handleAddExtension = async (_event: IpcRendererEvent, ...args: unknown[]) => { + const link = args[0] as string; try { console.log(`Received add-extension event with link: ${link}`); const command = extractCommand(link); @@ -401,7 +412,7 @@ export default function App() { }, [STRICT_ALLOWLIST]); useEffect(() => { - const handleFocusInput = (_event: IpcRendererEvent) => { + const handleFocusInput = (_event: IpcRendererEvent, ..._args: unknown[]) => { const inputField = document.querySelector('input[type="text"], textarea') as HTMLInputElement; if (inputField) { inputField.focus(); @@ -418,7 +429,9 @@ export default function App() { console.log(`Confirming installation of extension from: ${pendingLink}`); setModalVisible(false); try { - await addExtensionFromDeepLinkV2(pendingLink, addExtension, setView); + await addExtensionFromDeepLinkV2(pendingLink, addExtension, (view: string, options) => { + setView(view as View, options as ViewOptions); + }); console.log('Extension installation successful'); } catch (error) { console.error('Failed to add extension:', error); @@ -522,7 +535,9 @@ export default function App() { {view === 'schedules' && setView('chat')} />} {view === 'sharedSession' && ( setView('sessions')} @@ -532,7 +547,9 @@ export default function App() { try { await openSharedSessionFromDeepLink( `goose://sessions/${viewOptions.shareToken}`, - setView, + (view: View, options?: SessionLinksViewOptions) => { + setView(view, options as ViewOptions); + }, viewOptions.baseUrl ); } catch (error) { @@ -546,7 +563,7 @@ export default function App() { )} {view === 'recipeEditor' && ( )} {view === 'permission' && ( diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx index 77f4a2bb63f0..1365610e37fe 100644 --- a/ui/desktop/src/components/ChatInput.tsx +++ b/ui/desktop/src/components/ChatInput.tsx @@ -365,7 +365,7 @@ export default function ChatInput({ LocalMessageStorage.addMessage(validPastedImageFilesPaths.join(' ')); } - handleSubmit(new CustomEvent('submit', { detail: { value: textToSend } })); + handleSubmit(new CustomEvent('submit', { detail: { value: textToSend } }) as unknown as React.FormEvent); setDisplayValue(''); setValue(''); @@ -502,7 +502,7 @@ export default function ChatInput({ className="absolute -top-1 -right-1 bg-gray-700 hover:bg-red-600 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs leading-none opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity z-10" aria-label="Remove image" > - + )} diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index 0e54d0a1911e..381174c93d95 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -244,12 +244,20 @@ function ChatContent({ // Create a new window for the recipe editor console.log('Opening recipe editor with config:', response.recipe); + const recipeConfig = { + id: response.recipe.title || 'untitled', + name: response.recipe.title || 'Untitled Recipe', + description: response.recipe.description || '', + instructions: response.recipe.instructions || '', + activities: response.recipe.activities || [], + prompt: response.recipe.prompt || '', + }; window.electron.createChatWindow( undefined, // query undefined, // dir undefined, // version undefined, // resumeSessionId - response.recipe, // recipe config + recipeConfig, // recipe config 'recipeEditor' // view type ); @@ -272,11 +280,8 @@ function ChatContent({ // Update chat messages when they change and save to sessionStorage useEffect(() => { - setChat((prevChat: ChatType) => { - const updatedChat = { ...prevChat, messages }; - return updatedChat; - }); - }, [messages, setChat]); + setChat({ ...chat, messages }); + }, [messages, setChat, chat]); useEffect(() => { if (messages.length > 0) { @@ -467,7 +472,7 @@ function ChatContent({ const fetchSessionTokens = async () => { try { const sessionDetails = await fetchSessionDetails(chat.id); - setSessionTokenCount(sessionDetails.metadata.total_tokens); + setSessionTokenCount(sessionDetails.metadata.total_tokens || 0); } catch (err) { console.error('Error fetching session token count:', err); } diff --git a/ui/desktop/src/components/ConfigContext.tsx b/ui/desktop/src/components/ConfigContext.tsx index 82032deea118..6e2a8032dfbf 100644 --- a/ui/desktop/src/components/ConfigContext.tsx +++ b/ui/desktop/src/components/ConfigContext.tsx @@ -148,7 +148,7 @@ export const ConfigProvider: React.FC = ({ children }) => { return extensionsList; } - const extensionResponse: ExtensionResponse = result.data; + const extensionResponse: ExtensionResponse = result.data!; setExtensionsList(extensionResponse.extensions); return extensionResponse.extensions; } @@ -173,8 +173,8 @@ export const ConfigProvider: React.FC = ({ children }) => { async (forceRefresh = false): Promise => { if (forceRefresh || providersList.length === 0) { const response = await providers(); - setProvidersList(response.data); - return response.data; + setProvidersList(response.data || []); + return response.data || []; } return providersList; }, @@ -191,7 +191,7 @@ export const ConfigProvider: React.FC = ({ children }) => { // Load providers try { const providersResponse = await providers(); - setProvidersList(providersResponse.data); + setProvidersList(providersResponse.data || []); } catch (error) { console.error('Failed to load providers:', error); } diff --git a/ui/desktop/src/components/ErrorBoundary.tsx b/ui/desktop/src/components/ErrorBoundary.tsx index 1c29d2a6b521..4b80cd321e6c 100644 --- a/ui/desktop/src/components/ErrorBoundary.tsx +++ b/ui/desktop/src/components/ErrorBoundary.tsx @@ -51,7 +51,7 @@ export function ErrorUI({ error }: { error: Error }) { export class ErrorBoundary extends React.Component< { children: React.ReactNode }, - { error: Error; hasError: boolean } + { error: Error | null; hasError: boolean } > { constructor(props: { children: React.ReactNode }) { super(props); @@ -69,7 +69,7 @@ export class ErrorBoundary extends React.Component< render() { if (this.state.hasError) { - return ; + return ; } return this.props.children; } diff --git a/ui/desktop/src/components/FlappyGoose.tsx b/ui/desktop/src/components/FlappyGoose.tsx index 5cd9cb35534a..f9b8d54ae6c6 100644 --- a/ui/desktop/src/components/FlappyGoose.tsx +++ b/ui/desktop/src/components/FlappyGoose.tsx @@ -1,11 +1,5 @@ import React, { useEffect, useRef, useState, useCallback } from 'react'; -declare var requestAnimationFrame: (callback: FrameRequestCallback) => number; -declare class HTMLCanvasElement {} -declare class HTMLImageElement {} -declare class DOMHighResTimeStamp {} -declare class Image {} -declare type FrameRequestCallback = (time: DOMHighResTimeStamp) => void; import svg1 from '../images/loading-goose/1.svg'; import svg7 from '../images/loading-goose/7.svg'; @@ -20,9 +14,11 @@ interface FlappyGooseProps { } const FlappyGoose: React.FC = ({ onClose }) => { - const canvasRef = useRef(null); + // eslint-disable-next-line no-undef + const canvasRef = useRef(null); const [gameOver, setGameOver] = useState(false); const [displayScore, setDisplayScore] = useState(0); + // eslint-disable-next-line no-undef const gooseImages = useRef([]); const framesLoaded = useRef(0); const [imagesReady, setImagesReady] = useState(false); @@ -51,7 +47,7 @@ const FlappyGoose: React.FC = ({ onClose }) => { const OBSTACLE_WIDTH = 40; const FLAP_DURATION = 150; - const safeRequestAnimationFrame = useCallback((callback: FrameRequestCallback) => { + const safeRequestAnimationFrame = useCallback((callback: (time: number) => void) => { if (typeof window !== 'undefined' && typeof requestAnimationFrame !== 'undefined') { requestAnimationFrame(callback); } @@ -216,6 +212,7 @@ const FlappyGoose: React.FC = ({ onClose }) => { useEffect(() => { const frames = [svg1, svg7]; frames.forEach((src, index) => { + // eslint-disable-next-line no-undef const img = new Image() as HTMLImageElement; img.src = src; img.onload = () => { @@ -272,7 +269,9 @@ const FlappyGoose: React.FC = ({ onClose }) => { onClick={flap} > { + canvasRef.current = el; + }} style={{ border: '2px solid #333', borderRadius: '8px', diff --git a/ui/desktop/src/components/GooseMessage.tsx b/ui/desktop/src/components/GooseMessage.tsx index 6066386b3945..4b1de364d565 100644 --- a/ui/desktop/src/components/GooseMessage.tsx +++ b/ui/desktop/src/components/GooseMessage.tsx @@ -190,7 +190,7 @@ export default function GooseMessage({ {/* NOTE from alexhancock on 1/14/2025 - disabling again temporarily due to non-determinism in when the forms show up */} {false && metadata && (
- +
)} diff --git a/ui/desktop/src/components/GooseResponseForm.tsx b/ui/desktop/src/components/GooseResponseForm.tsx index 7c76d53e8a63..34d36572eaab 100644 --- a/ui/desktop/src/components/GooseResponseForm.tsx +++ b/ui/desktop/src/components/GooseResponseForm.tsx @@ -132,9 +132,9 @@ export default function GooseResponseForm({ return null; } - function isForm(f: DynamicForm) { + function isForm(f: DynamicForm | null): f is DynamicForm { return ( - f && f.title && f.description && f.fields && Array.isArray(f.fields) && f.fields.length > 0 + !!f && !!f.title && !!f.description && !!f.fields && Array.isArray(f.fields) && f.fields.length > 0 ); } diff --git a/ui/desktop/src/components/GoosehintsModal.tsx b/ui/desktop/src/components/GoosehintsModal.tsx index 19624efb37d3..f7cab002c492 100644 --- a/ui/desktop/src/components/GoosehintsModal.tsx +++ b/ui/desktop/src/components/GoosehintsModal.tsx @@ -96,9 +96,9 @@ type GoosehintsModalProps = { export const GoosehintsModal = ({ directory, setIsGoosehintsModalOpen }: GoosehintsModalProps) => { const goosehintsFilePath = `${directory}/.goosehints`; - const [goosehintsFile, setGoosehintsFile] = useState(null); + const [goosehintsFile, setGoosehintsFile] = useState(''); const [goosehintsFileFound, setGoosehintsFileFound] = useState(false); - const [goosehintsFileReadError, setGoosehintsFileReadError] = useState(null); + const [goosehintsFileReadError, setGoosehintsFileReadError] = useState(''); useEffect(() => { const fetchGoosehintsFile = async () => { @@ -106,7 +106,7 @@ export const GoosehintsModal = ({ directory, setIsGoosehintsModalOpen }: Goosehi const { file, error, found } = await getGoosehintsFile(goosehintsFilePath); setGoosehintsFile(file); setGoosehintsFileFound(found); - setGoosehintsFileReadError(error); + setGoosehintsFileReadError(error || ''); } catch (error) { console.error('Error fetching .goosehints file:', error); } @@ -125,7 +125,7 @@ export const GoosehintsModal = ({ directory, setIsGoosehintsModalOpen }: Goosehi
{goosehintsFileReadError ? ( - + ) : (
diff --git a/ui/desktop/src/components/LinkPreview.tsx b/ui/desktop/src/components/LinkPreview.tsx index f4fc835b4651..355d9154eebc 100644 --- a/ui/desktop/src/components/LinkPreview.tsx +++ b/ui/desktop/src/components/LinkPreview.tsx @@ -54,9 +54,9 @@ async function fetchMetadata(url: string): Promise { return { title: title || url, - description, + description: description || undefined, favicon, - image, + image: image || undefined, url, }; } catch (error) { diff --git a/ui/desktop/src/components/ProviderGrid.tsx b/ui/desktop/src/components/ProviderGrid.tsx index 1e3d6721478d..4437d712cfdd 100644 --- a/ui/desktop/src/components/ProviderGrid.tsx +++ b/ui/desktop/src/components/ProviderGrid.tsx @@ -46,7 +46,7 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) { const handleConfigure = async (provider: { id: string; name: string; isConfigured: boolean; description: string }) => { const providerId = provider.id.toLowerCase(); - const modelName = getDefaultModel(providerId); + const modelName = getDefaultModel(providerId) || 'default-model'; const model = createSelectedModel(providerId, modelName); await initializeSystem(providerId, model.name); @@ -189,9 +189,9 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) { {showSetupModal && selectedId && (
p.id === selectedId)?.name} - model="Example Model" - endpoint="Example Endpoint" + provider={providers.find((p) => p.id === selectedId)?.name || 'Unknown Provider'} + _model="Example Model" + _endpoint="Example Endpoint" onSubmit={handleModalSubmit} onCancel={() => { setShowSetupModal(false); diff --git a/ui/desktop/src/components/RecipeEditor.tsx b/ui/desktop/src/components/RecipeEditor.tsx index a621d32cb24d..9b183015fd4c 100644 --- a/ui/desktop/src/components/RecipeEditor.tsx +++ b/ui/desktop/src/components/RecipeEditor.tsx @@ -121,13 +121,13 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { if (!extension) return null; // Create a clean copy of the extension configuration - const cleanExtension = { ...extension }; - delete cleanExtension.enabled; + const { enabled: _enabled, ...cleanExtension } = extension; // Remove legacy envs which could potentially include secrets // env_keys will work but rely on the end user having setup those keys themselves if ('envs' in cleanExtension) { // eslint-disable-next-line @typescript-eslint/no-explicit-any - delete (cleanExtension as any).envs; + const { envs: _envs, ...finalExtension } = cleanExtension as any; + return finalExtension; } return cleanExtension; }) diff --git a/ui/desktop/src/components/conversation/SearchBar.tsx b/ui/desktop/src/components/conversation/SearchBar.tsx index 6c3b91ee8177..2c47e5559192 100644 --- a/ui/desktop/src/components/conversation/SearchBar.tsx +++ b/ui/desktop/src/components/conversation/SearchBar.tsx @@ -69,13 +69,13 @@ export const SearchBar: React.FC = ({ } }, [initialSearchTerm, caseSensitive, debouncedSearchRef]); - const [localSearchResults, setLocalSearchResults] = useState(null); + const [localSearchResults, setLocalSearchResults] = useState(undefined); // Sync external search results with local state useEffect(() => { // Only set results if we have a search term if (!searchTerm) { - setLocalSearchResults(null); + setLocalSearchResults(undefined); } else { setLocalSearchResults(searchResults); } diff --git a/ui/desktop/src/components/conversation/SearchView.tsx b/ui/desktop/src/components/conversation/SearchView.tsx index 5024a3328bf5..2570fc5df54f 100644 --- a/ui/desktop/src/components/conversation/SearchView.tsx +++ b/ui/desktop/src/components/conversation/SearchView.tsx @@ -314,7 +314,7 @@ export const SearchView: React.FC> = ({
{ if (el) { - containerRef.current = el; + containerRef.current = el as SearchContainerElement; // Expose the highlighter instance containerRef.current._searchHighlighter = highlighterRef.current; } @@ -326,7 +326,7 @@ export const SearchView: React.FC> = ({ onSearch={handleSearch} onClose={handleCloseSearch} onNavigate={handleNavigate} - searchResults={searchResults || internalSearchResults} + searchResults={searchResults || internalSearchResults || undefined} inputRef={searchInputRef} initialSearchTerm={initialSearchTerm} /> diff --git a/ui/desktop/src/components/more_menu/MoreMenu.tsx b/ui/desktop/src/components/more_menu/MoreMenu.tsx index 7424b06680f8..a54ed55238f8 100644 --- a/ui/desktop/src/components/more_menu/MoreMenu.tsx +++ b/ui/desktop/src/components/more_menu/MoreMenu.tsx @@ -5,6 +5,15 @@ import { FolderOpen, Moon, Sliders, Sun } from 'lucide-react'; import { useConfig } from '../ConfigContext'; import { ViewOptions, View } from '../../App'; +interface RecipeConfig { + id: string; + name: string; + description: string; + instructions?: string; + activities?: string[]; + [key: string]: unknown; +} + interface MenuButtonProps { onClick: () => void; children: React.ReactNode; @@ -187,7 +196,7 @@ export default function MoreMenu({ setOpen(false); window.electron.createChatWindow( undefined, - window.appConfig.get('GOOSE_WORKING_DIR') + window.appConfig.get('GOOSE_WORKING_DIR') as string | undefined ); }} subtitle="Start a new session in the current directory" @@ -244,7 +253,7 @@ export default function MoreMenu({ undefined, // dir undefined, // version undefined, // resumeSessionId - recipeConfig, // recipe config + recipeConfig as RecipeConfig, // recipe config 'recipeEditor' // view type ); }} diff --git a/ui/desktop/src/components/more_menu/MoreMenuLayout.tsx b/ui/desktop/src/components/more_menu/MoreMenuLayout.tsx index b030d65f96d4..7095066c97a2 100644 --- a/ui/desktop/src/components/more_menu/MoreMenuLayout.tsx +++ b/ui/desktop/src/components/more_menu/MoreMenuLayout.tsx @@ -44,7 +44,7 @@ export default function MoreMenuLayout({ >
- {window.appConfig.get('GOOSE_WORKING_DIR')} + {String(window.appConfig.get('GOOSE_WORKING_DIR'))}
@@ -54,7 +54,10 @@ export default function MoreMenuLayout({ - + {})} + setIsGoosehintsModalOpen={setIsGoosehintsModalOpen || (() => {})} + />
)}
diff --git a/ui/desktop/src/components/schedule/CreateScheduleModal.tsx b/ui/desktop/src/components/schedule/CreateScheduleModal.tsx index 8aacc960ff83..39b80351abc4 100644 --- a/ui/desktop/src/components/schedule/CreateScheduleModal.tsx +++ b/ui/desktop/src/components/schedule/CreateScheduleModal.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, FormEvent } from 'react'; import { Card } from '../ui/card'; import { Button } from '../ui/button'; import { Input } from '../ui/input'; -import { Select } from '../ui/select'; +import { Select } from '../ui/Select'; import cronstrue from 'cronstrue'; type FrequencyValue = 'once' | 'hourly' | 'daily' | 'weekly' | 'monthly'; @@ -292,7 +292,8 @@ export const CreateScheduleModal: React.FC = ({ instanceId="frequency-select-modal" options={frequencies} value={frequencies.find((f) => f.value === frequency)} - onChange={(selectedOption: FrequencyOption | null) => { + onChange={(newValue: unknown) => { + const selectedOption = newValue as FrequencyOption | null; if (selectedOption) setFrequency(selectedOption.value); }} placeholder="Select frequency..." diff --git a/ui/desktop/src/components/schedule/EditScheduleModal.tsx b/ui/desktop/src/components/schedule/EditScheduleModal.tsx index 4859180b02ff..c51815d671d7 100644 --- a/ui/desktop/src/components/schedule/EditScheduleModal.tsx +++ b/ui/desktop/src/components/schedule/EditScheduleModal.tsx @@ -287,7 +287,8 @@ export const EditScheduleModal: React.FC = ({ instanceId="frequency-select-modal" options={frequencies} value={frequencies.find((f) => f.value === frequency)} - onChange={(selectedOption: FrequencyOption | null) => { + onChange={(newValue: unknown) => { + const selectedOption = newValue as FrequencyOption | null; if (selectedOption) setFrequency(selectedOption.value); }} placeholder="Select frequency..." diff --git a/ui/desktop/src/components/sessions/SharedSessionView.tsx b/ui/desktop/src/components/sessions/SharedSessionView.tsx index 7fa04bffb632..d13753ec2e64 100644 --- a/ui/desktop/src/components/sessions/SharedSessionView.tsx +++ b/ui/desktop/src/components/sessions/SharedSessionView.tsx @@ -33,13 +33,13 @@ const SharedSessionView: React.FC = ({
- {formatMessageTimestamp(session.messages[0]?.created)} + {session ? formatMessageTimestamp(session.messages[0]?.created) : 'Unknown'} - {session.message_count} + {session ? session.message_count : 0} - {session.total_tokens !== null && ( + {session && session.total_tokens !== null && ( {session.total_tokens.toLocaleString()} @@ -49,7 +49,7 @@ const SharedSessionView: React.FC = ({
- {session.working_dir} + {session ? session.working_dir : 'Unknown'}
diff --git a/ui/desktop/src/components/settings/ProviderSetupModal.tsx b/ui/desktop/src/components/settings/ProviderSetupModal.tsx index 00ecfc80d8e8..28566e9815c1 100644 --- a/ui/desktop/src/components/settings/ProviderSetupModal.tsx +++ b/ui/desktop/src/components/settings/ProviderSetupModal.tsx @@ -29,7 +29,7 @@ export function ProviderSetupModal({ const [configValues, setConfigValues] = React.useState<{ [key: string]: string }>( default_key_value ); - const requiredKeys = required_keys[provider] || ['API Key']; + const requiredKeys = (required_keys as Record)[provider] || ['API Key']; const headerText = title || `Setup ${provider}`; const shouldShowBattle = React.useMemo(() => { @@ -59,7 +59,7 @@ export function ProviderSetupModal({ ) : (
- {requiredKeys.map((keyName) => ( + {requiredKeys.map((keyName: string) => (
{ const configStatus = provider.config_status ?? {}; // Skip if provider isn't in required_keys - if (!required_keys[providerName]) return false; + if (!required_keys[providerName as keyof typeof required_keys]) return false; // Get all required keys for this provider - const providerRequiredKeys = required_keys[providerName]; + const providerRequiredKeys = required_keys[providerName as keyof typeof required_keys]; // Special case: If a provider has exactly one required key and that key // has a default value, check if it's explicitly set @@ -103,14 +103,17 @@ export async function getConfigSettings(): Promise>((acc: Record, key: string) => { - acc[key] = { - key, - is_set: provider.is_configured, - location: provider.is_configured ? 'config' : undefined, - }; - return acc; - }, {}), + config_status: providerRequiredKeys.reduce>( + (acc: Record, key: string) => { + acc[key] = { + key, + is_set: provider.is_configured, + location: provider.is_configured ? 'config' : undefined, + }; + return acc; + }, + {} + ), }; }); diff --git a/ui/desktop/src/components/settings/basic/ConfigureApproveMode.tsx b/ui/desktop/src/components/settings/basic/ConfigureApproveMode.tsx index f0fbe20d4f61..a7b61e2b3788 100644 --- a/ui/desktop/src/components/settings/basic/ConfigureApproveMode.tsx +++ b/ui/desktop/src/components/settings/basic/ConfigureApproveMode.tsx @@ -39,7 +39,7 @@ export function ConfigureApproveMode({ setIsSubmitting(true); try { - handleModeChange(approveMode); + handleModeChange(approveMode || ''); onClose(); } catch (error) { console.error('Error configuring goose mode:', error); @@ -71,7 +71,7 @@ export function ConfigureApproveMode({ key={mode.key} mode={mode} showDescription={true} - currentMode={approveMode} + currentMode={approveMode || ''} isApproveModeConfigure={true} handleModeChange={(newMode) => { setApproveMode(newMode); diff --git a/ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx b/ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx index e8e3494a3fd3..a8af5dcdafc3 100644 --- a/ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx +++ b/ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx @@ -38,7 +38,7 @@ export function ConfigureBuiltInExtensionModal({ setIsSubmitting(true); try { // First store all environment variables - if (extension.env_keys?.length > 0) { + if (extension.env_keys && extension.env_keys.length > 0) { for (const envKey of extension.env_keys) { const value = envValues[envKey]; if (!value) continue; @@ -103,13 +103,13 @@ export function ConfigureBuiltInExtensionModal({ {/* Form */}
- {extension.env_keys?.length > 0 ? ( + {extension.env_keys && extension.env_keys.length > 0 ? ( <>

Please provide the required environment variables for this extension:

- {extension.env_keys?.map((envVarName) => ( + {extension.env_keys.map((envVarName) => (