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 1ae966fcbbfb..e4418b1caaef 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -54,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; }; @@ -239,15 +239,18 @@ export default function App() { }, []); useEffect(() => { - const handleOpenSharedSession = async (event: IpcRendererEvent, ...args: unknown[]) => { + 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, (view: View, options?: SessionLinksViewOptions) => { - setView(view, options as ViewOptions); - }); + await openSharedSessionFromDeepLink( + link, + (view: View, options?: SessionLinksViewOptions) => { + setView(view, options as ViewOptions); + } + ); } catch (error) { console.error('Unexpected error opening shared session:', error); setView('sessions'); @@ -284,7 +287,7 @@ export default function App() { useEffect(() => { console.log('Setting up fatal error handler'); - const handleFatalError = (event: IpcRendererEvent, ...args: unknown[]) => { + const handleFatalError = (_event: IpcRendererEvent, ...args: unknown[]) => { const errorMessage = args[0] as string; console.error('Encountered a fatal error: ', errorMessage); console.error('Current view:', view); @@ -299,7 +302,7 @@ export default function App() { useEffect(() => { console.log('Setting up view change handler'); - const handleSetView = (event: IpcRendererEvent, ...args: unknown[]) => { + const handleSetView = (_event: IpcRendererEvent, ...args: unknown[]) => { const newView = args[0] as View; console.log(`Received view change request to: ${newView}`); setView(newView); @@ -335,7 +338,7 @@ export default function App() { useEffect(() => { console.log('Setting up extension handler'); - const handleAddExtension = async (event: IpcRendererEvent, ...args: unknown[]) => { + const handleAddExtension = async (_event: IpcRendererEvent, ...args: unknown[]) => { const link = args[0] as string; try { console.log(`Received add-extension event with link: ${link}`); @@ -532,7 +535,9 @@ export default function App() { {view === 'schedules' && setView('chat')} />} {view === 'sharedSession' && ( setView('sessions')} diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index 6c01d8955921..381174c93d95 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -244,18 +244,11 @@ function ChatContent({ // Create a new window for the recipe editor console.log('Opening recipe editor with config:', response.recipe); - const recipeConfig: { - id: string; - title: string; - description: string; - instructions: string; - activities: string[]; - prompt: string; - } = { + const recipeConfig = { id: response.recipe.title || 'untitled', - title: response.recipe.title, - description: response.recipe.description, - instructions: response.recipe.instructions, + name: response.recipe.title || 'Untitled Recipe', + description: response.recipe.description || '', + instructions: response.recipe.instructions || '', activities: response.recipe.activities || [], prompt: response.recipe.prompt || '', }; @@ -287,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) { 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/GooseResponseForm.tsx b/ui/desktop/src/components/GooseResponseForm.tsx index b7199da54a83..34d36572eaab 100644 --- a/ui/desktop/src/components/GooseResponseForm.tsx +++ b/ui/desktop/src/components/GooseResponseForm.tsx @@ -134,7 +134,7 @@ export default function GooseResponseForm({ 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/ProviderGrid.tsx b/ui/desktop/src/components/ProviderGrid.tsx index 81ab47e1d083..4437d712cfdd 100644 --- a/ui/desktop/src/components/ProviderGrid.tsx +++ b/ui/desktop/src/components/ProviderGrid.tsx @@ -190,8 +190,8 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) {
p.id === selectedId)?.name || 'Unknown Provider'} - model="Example Model" - endpoint="Example Endpoint" + _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/SearchView.tsx b/ui/desktop/src/components/conversation/SearchView.tsx index eb1c51d44471..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; } diff --git a/ui/desktop/src/components/more_menu/MoreMenu.tsx b/ui/desktop/src/components/more_menu/MoreMenu.tsx index 04e40aa4b378..a54ed55238f8 100644 --- a/ui/desktop/src/components/more_menu/MoreMenu.tsx +++ b/ui/desktop/src/components/more_menu/MoreMenu.tsx @@ -7,10 +7,11 @@ import { ViewOptions, View } from '../../App'; interface RecipeConfig { id: string; - title: string; + name: string; description: string; - instructions: string; - activities: string[]; + instructions?: string; + activities?: string[]; + [key: string]: unknown; } interface MenuButtonProps { @@ -252,7 +253,7 @@ export default function MoreMenu({ undefined, // dir undefined, // version undefined, // resumeSessionId - recipeConfig as RecipeConfig | undefined, // recipe config + recipeConfig as RecipeConfig, // recipe config 'recipeEditor' // view type ); }} diff --git a/ui/desktop/src/components/schedule/CreateScheduleModal.tsx b/ui/desktop/src/components/schedule/CreateScheduleModal.tsx index 138d13475d66..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'; 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 21dccd09aee1..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(() => { diff --git a/ui/desktop/src/components/settings/SettingsView.tsx b/ui/desktop/src/components/settings/SettingsView.tsx index b2220db357ef..b0c0c225f318 100644 --- a/ui/desktop/src/components/settings/SettingsView.tsx +++ b/ui/desktop/src/components/settings/SettingsView.tsx @@ -46,7 +46,6 @@ const DEFAULT_SETTINGS: SettingsType = { enabled: true, }, ], - // @ts-expect-error "we actually do always have all the properties required for builtins, but tsc cannot tell for some reason" extensions: BUILT_IN_EXTENSIONS, }; diff --git a/ui/desktop/src/components/settings/api_keys/utils.tsx b/ui/desktop/src/components/settings/api_keys/utils.tsx index 3cfb5051aa66..1f7d9d1536fe 100644 --- a/ui/desktop/src/components/settings/api_keys/utils.tsx +++ b/ui/desktop/src/components/settings/api_keys/utils.tsx @@ -40,10 +40,10 @@ export async function getActiveProviders(): Promise { 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/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) => (