Fix remaining typescript errors (#2741)

This commit is contained in:
Zane
2025-05-30 12:06:49 -07:00
committed by GitHub
parent 2c0cda7bec
commit 0c042010ff
109 changed files with 1873 additions and 1438 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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",
@@ -67,12 +67,14 @@
"postcss": "^8.4.47",
"prettier": "^3.4.2",
"tailwindcss": "^3.4.14",
"typescript": "~5.5.0",
"vite": "^6.3.4"
},
"keywords": [],
"license": "Apache-2.0",
"lint-staged": {
"src/**/*.{ts,tsx}": [
"bash -c 'npm run typecheck'",
"eslint --fix --max-warnings 0 --no-warn-ignored",
"prettier --write"
],

View File

@@ -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';
@@ -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' && <SchedulesView onClose={() => setView('chat')} />}
{view === 'sharedSession' && (
<SharedSessionView
session={viewOptions?.sessionDetails}
session={
(viewOptions?.sessionDetails as unknown as SharedSessionDetails | null) || null
}
isLoading={isLoadingSharedSession}
error={viewOptions?.error || sharedSessionError}
onBack={() => 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' && (
<RecipeEditor
config={viewOptions?.config || window.electron.getConfig().recipeConfig}
config={(viewOptions?.config as Recipe) || window.electron.getConfig().recipeConfig}
/>
)}
{view === 'permission' && (

View File

@@ -1,4 +1,3 @@
interface AgentHeaderProps {
title: string;
profileInfo?: string;

View File

@@ -94,11 +94,7 @@ export default function ChatInput({
// Set the image to loading state
setPastedImages((prev) =>
prev.map((img) =>
img.id === imageId
? { ...img, isLoading: true, error: undefined }
: img
)
prev.map((img) => (img.id === imageId ? { ...img, isLoading: true, error: undefined } : img))
);
try {
@@ -149,7 +145,8 @@ export default function ChatInput({
// Debounced function to update actual value
const debouncedSetValue = useMemo(
() => debounce((value: string) => {
() =>
debounce((value: string) => {
setValue(value);
}, 150),
[setValue]
@@ -157,7 +154,8 @@ export default function ChatInput({
// Debounced autosize function
const debouncedAutosize = useMemo(
() => debounce((element: HTMLTextAreaElement) => {
() =>
debounce((element: HTMLTextAreaElement) => {
element.style.height = '0px'; // Reset height
const scrollHeight = element.scrollHeight;
element.style.height = Math.min(scrollHeight, maxHeight) + 'px';
@@ -179,7 +177,7 @@ export default function ChatInput({
const handlePaste = async (evt: React.ClipboardEvent<HTMLTextAreaElement>) => {
const files = Array.from(evt.clipboardData.files || []);
const imageFiles = files.filter(file => file.type.startsWith('image/'));
const imageFiles = files.filter((file) => file.type.startsWith('image/'));
if (imageFiles.length === 0) return;
@@ -192,13 +190,13 @@ export default function ChatInput({
id: `error-${Date.now()}`,
dataUrl: '',
isLoading: false,
error: `Cannot paste ${imageFiles.length} image(s). Maximum ${MAX_IMAGES_PER_MESSAGE} images per message allowed.`
}
error: `Cannot paste ${imageFiles.length} image(s). Maximum ${MAX_IMAGES_PER_MESSAGE} images per message allowed.`,
},
]);
// Remove the error message after 3 seconds
setTimeout(() => {
setPastedImages((prev) => prev.filter(img => !img.id.startsWith('error-')));
setPastedImages((prev) => prev.filter((img) => !img.id.startsWith('error-')));
}, 3000);
return;
@@ -216,13 +214,13 @@ export default function ChatInput({
id: errorId,
dataUrl: '',
isLoading: false,
error: `Image too large (${Math.round(file.size / (1024 * 1024))}MB). Maximum ${MAX_IMAGE_SIZE_MB}MB allowed.`
}
error: `Image too large (${Math.round(file.size / (1024 * 1024))}MB). Maximum ${MAX_IMAGE_SIZE_MB}MB allowed.`,
},
]);
// Remove the error message after 3 seconds
setTimeout(() => {
setPastedImages((prev) => prev.filter(img => img.id !== errorId));
setPastedImages((prev) => prev.filter((img) => img.id !== errorId));
}, 3000);
continue;
@@ -365,7 +363,9 @@ 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"
>
<Close size={14} />
<Close className="w-3.5 h-3.5" />
</button>
)}
</div>

View File

@@ -34,6 +34,7 @@ import {
ToolResponseMessageContent,
ToolConfirmationRequestMessageContent,
getTextContent,
TextContent,
} from '../types/message';
export interface ChatType {
@@ -245,12 +246,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
);
@@ -273,11 +282,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) {
@@ -354,10 +360,11 @@ function ChatContent({
// check if the last message is a real user's message
if (lastMessage && isUserMessage(lastMessage) && !isToolResponse) {
// Get the text content from the last message before removing it
const textContent = lastMessage.content.find((c) => c.type === 'text')?.text || '';
const textContent = lastMessage.content.find((c): c is TextContent => c.type === 'text');
const textValue = textContent?.text || '';
// Set the text back to the input field
_setInput(textContent);
_setInput(textValue);
// Remove the last user message if it's the most recent one
if (messages.length > 1) {
@@ -453,7 +460,8 @@ function ChatContent({
return filteredMessages
.reduce<string[]>((history, message) => {
if (isUserMessage(message)) {
const text = message.content.find((c) => c.type === 'text')?.text?.trim();
const textContent = message.content.find((c): c is TextContent => c.type === 'text');
const text = textContent?.text?.trim();
if (text) {
history.push(text);
}
@@ -468,7 +476,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);
}
@@ -535,7 +543,7 @@ function ChatContent({
{messages.length === 0 ? (
<Splash
append={append}
activities={Array.isArray(recipeConfig?.activities) ? recipeConfig.activities : null}
activities={Array.isArray(recipeConfig?.activities) ? recipeConfig!.activities : null}
title={recipeConfig?.title}
/>
) : (

View File

@@ -148,7 +148,7 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ 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<ConfigProviderProps> = ({ children }) => {
async (forceRefresh = false): Promise<ProviderDetails[]> => {
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<ConfigProviderProps> = ({ children }) => {
// Load providers
try {
const providersResponse = await providers();
setProvidersList(providersResponse.data);
setProvidersList(providersResponse.data || []);
} catch (error) {
console.error('Failed to load providers:', error);
}

View File

@@ -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 <ErrorUI error={this.state.error} />;
return <ErrorUI error={this.state.error || new Error('Unknown error')} />;
}
return this.props.children;
}

View File

@@ -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<FlappyGooseProps> = ({ onClose }) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
// eslint-disable-next-line no-undef
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const [gameOver, setGameOver] = useState(false);
const [displayScore, setDisplayScore] = useState(0);
// eslint-disable-next-line no-undef
const gooseImages = useRef<HTMLImageElement[]>([]);
const framesLoaded = useRef(0);
const [imagesReady, setImagesReady] = useState(false);
@@ -51,7 +47,7 @@ const FlappyGoose: React.FC<FlappyGooseProps> = ({ 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<FlappyGooseProps> = ({ 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<FlappyGooseProps> = ({ onClose }) => {
onClick={flap}
>
<canvas
ref={canvasRef}
ref={(el) => {
canvasRef.current = el;
}}
style={{
border: '2px solid #333',
borderRadius: '8px',

View File

@@ -6,7 +6,11 @@ interface GooseLogoProps {
hover?: boolean;
}
export default function GooseLogo({ className = '', size = 'default', hover = true }: GooseLogoProps) {
export default function GooseLogo({
className = '',
size = 'default',
hover = true,
}: GooseLogoProps) {
const sizes = {
default: {
frame: 'w-16 h-16',

View File

@@ -194,7 +194,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 && (
<div className="flex mt-[16px]">
<GooseResponseForm message={textContent} metadata={metadata} append={append} />
<GooseResponseForm message={textContent} metadata={metadata || null} append={append} />
</div>
)}
</div>

View File

@@ -132,9 +132,14 @@ 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
);
}

View File

@@ -96,9 +96,9 @@ type GoosehintsModalProps = {
export const GoosehintsModal = ({ directory, setIsGoosehintsModalOpen }: GoosehintsModalProps) => {
const goosehintsFilePath = `${directory}/.goosehints`;
const [goosehintsFile, setGoosehintsFile] = useState<string>(null);
const [goosehintsFile, setGoosehintsFile] = useState<string>('');
const [goosehintsFileFound, setGoosehintsFileFound] = useState<boolean>(false);
const [goosehintsFileReadError, setGoosehintsFileReadError] = useState<string>(null);
const [goosehintsFileReadError, setGoosehintsFileReadError] = useState<string>('');
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
<ModalHelpText />
<div className="flex flex-col flex-1">
{goosehintsFileReadError ? (
<ModalError error={goosehintsFileReadError} />
<ModalError error={new Error(goosehintsFileReadError)} />
) : (
<div className="flex flex-col flex-1 space-y-2 h-full">
<ModalFileInfo filePath={goosehintsFilePath} found={goosehintsFileFound} />

View File

@@ -54,9 +54,9 @@ async function fetchMetadata(url: string): Promise<Metadata> {
return {
title: title || url,
description,
description: description || undefined,
favicon,
image,
image: image || undefined,
url,
};
} catch (error) {

View File

@@ -1,4 +1,3 @@
export function LoadingPlaceholder() {
return (
<div className="space-y-2">

View File

@@ -43,10 +43,15 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) {
});
}, [activeKeys]);
const handleConfigure = async (provider: { id: string; name: string; isConfigured: boolean; description: string }) => {
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);
@@ -63,7 +68,12 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) {
onSubmit?.();
};
const handleAddKeys = (provider: { id: string; name: string; isConfigured: boolean; description: string }) => {
const handleAddKeys = (provider: {
id: string;
name: string;
isConfigured: boolean;
description: string;
}) => {
setSelectedId(provider.id);
setShowSetupModal(true);
};
@@ -189,9 +199,9 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) {
{showSetupModal && selectedId && (
<div className="relative z-[9999]">
<ProviderSetupModal
provider={providers.find((p) => 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);

View File

@@ -123,13 +123,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;
})

View File

@@ -129,7 +129,7 @@ function ToolCallView({
const toolResults: { result: Content; isExpandToolResults: boolean }[] =
loadingStatus === 'success' && Array.isArray(toolResponse?.toolResult.value)
? toolResponse.toolResult.value
? toolResponse!.toolResult.value
.filter((item) => {
const audience = item.annotations?.audience as string[] | undefined;
return !audience || audience.includes('user');
@@ -322,7 +322,7 @@ function ToolLogsView({
working: boolean;
isStartExpanded?: boolean;
}) {
const boxRef = useRef(null);
const boxRef = useRef<HTMLDivElement>(null);
// Whenever logs update, jump to the newest entry
useEffect(() => {

View File

@@ -69,13 +69,13 @@ export const SearchBar: React.FC<SearchBarProps> = ({
}
}, [initialSearchTerm, caseSensitive, debouncedSearchRef]);
const [localSearchResults, setLocalSearchResults] = useState<typeof searchResults>(null);
const [localSearchResults, setLocalSearchResults] = useState<typeof searchResults>(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);
}

View File

@@ -314,7 +314,7 @@ export const SearchView: React.FC<PropsWithChildren<SearchViewProps>> = ({
<div
ref={(el) => {
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<PropsWithChildren<SearchViewProps>> = ({
onSearch={handleSearch}
onClose={handleCloseSearch}
onNavigate={handleNavigate}
searchResults={searchResults || internalSearchResults}
searchResults={searchResults || internalSearchResults || undefined}
inputRef={searchInputRef}
initialSearchTerm={initialSearchTerm}
/>

View File

@@ -1,4 +1,3 @@
export default function ArrowDown({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function ArrowUp({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function Attach({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function Back({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export function Bars() {
return (
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">

View File

@@ -1,4 +1,3 @@
export default function ChatSmart({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function Check({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function ChevronDown({ className }: { className?: string }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
interface Props {
className?: string;
// eslint-disable-next-line

View File

@@ -1,4 +1,3 @@
export default function ChevronUp({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function Close({ className }: { className?: string }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function Copy({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function Document({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function Edit({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export function Gear({ className = '' }: { className?: string }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
interface Props {
// eslint-disable-next-line
[key: string]: any; // This will allow any other SVG props to pass through

View File

@@ -1,4 +1,3 @@
export function Goose({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function Idea({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function More({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function Refresh({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function Send({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function SensitiveHidden({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function SensitiveVisible({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function Settings({ className = '' }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function Time({ className = '' }) {
return (
<svg

View File

@@ -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
);
}}

View File

@@ -44,7 +44,7 @@ export default function MoreMenuLayout({
>
<Document className="mr-1" />
<div className="max-w-[200px] truncate [direction:rtl]">
{window.appConfig.get('GOOSE_WORKING_DIR')}
{String(window.appConfig.get('GOOSE_WORKING_DIR'))}
</div>
</button>
</TooltipTrigger>
@@ -54,7 +54,10 @@ export default function MoreMenuLayout({
</Tooltip>
</TooltipProvider>
<MoreMenu setView={setView} setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} />
<MoreMenu
setView={setView || (() => {})}
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen || (() => {})}
/>
</div>
)}
</div>

View File

@@ -128,65 +128,63 @@ function recipeToYaml(recipe: Recipe): string {
}
if (recipe.extensions && recipe.extensions.length > 0) {
cleanRecipe.extensions = recipe.extensions.map(ext => {
cleanRecipe.extensions = recipe.extensions.map((ext) => {
const cleanExt: CleanExtension = {
name: ext.name,
type: 'builtin', // Default type, will be overridden below
};
// Handle different extension types
// Handle different extension types using type assertions
if ('type' in ext && ext.type) {
cleanExt.type = ext.type as CleanExtension['type'];
// Add type-specific fields based on the ExtensionConfig union types
switch (ext.type) {
case 'sse':
if ('uri' in ext && ext.uri) {
cleanExt.uri = ext.uri as string;
// Use type assertions to access properties safely
const extAny = ext as Record<string, unknown>;
if (ext.type === 'sse' && extAny.uri) {
cleanExt.uri = extAny.uri as string;
} else if (ext.type === 'stdio') {
if (extAny.cmd) {
cleanExt.cmd = extAny.cmd as string;
}
break;
case 'stdio':
if ('cmd' in ext && ext.cmd) {
cleanExt.cmd = ext.cmd as string;
if (extAny.args) {
cleanExt.args = extAny.args as string[];
}
if ('args' in ext && ext.args) {
cleanExt.args = ext.args as string[];
} else if (ext.type === 'builtin' && extAny.display_name) {
cleanExt.display_name = extAny.display_name as string;
}
break;
case 'builtin':
if ('display_name' in ext && ext.display_name) {
cleanExt.display_name = ext.display_name as string;
// Handle frontend type separately to avoid TypeScript narrowing issues
if ((ext.type as string) === 'frontend') {
if (extAny.tools) {
cleanExt.tools = extAny.tools as unknown[];
}
break;
case 'frontend':
if ('tools' in ext && ext.tools) {
cleanExt.tools = ext.tools as unknown[];
if (extAny.instructions) {
cleanExt.instructions = extAny.instructions as string;
}
if ('instructions' in ext && ext.instructions) {
cleanExt.instructions = ext.instructions as string;
}
break;
}
} else {
// Fallback: try to infer type from available fields
if ('cmd' in ext && ext.cmd) {
const extAny = ext as Record<string, unknown>;
if (extAny.cmd) {
cleanExt.type = 'stdio';
cleanExt.cmd = ext.cmd as string;
if ('args' in ext && ext.args) {
cleanExt.args = ext.args as string[];
cleanExt.cmd = extAny.cmd as string;
if (extAny.args) {
cleanExt.args = extAny.args as string[];
}
} else if ('command' in ext && ext.command) {
} else if (extAny.command) {
// Handle legacy 'command' field by converting to 'cmd'
cleanExt.type = 'stdio';
cleanExt.cmd = ext.command as string;
} else if ('uri' in ext && ext.uri) {
cleanExt.cmd = extAny.command as string;
} else if (extAny.uri) {
cleanExt.type = 'sse';
cleanExt.uri = ext.uri as string;
} else if ('tools' in ext && ext.tools) {
cleanExt.uri = extAny.uri as string;
} else if (extAny.tools) {
cleanExt.type = 'frontend';
cleanExt.tools = ext.tools as unknown[];
if ('instructions' in ext && ext.instructions) {
cleanExt.instructions = ext.instructions as string;
cleanExt.tools = extAny.tools as unknown[];
if (extAny.instructions) {
cleanExt.instructions = extAny.instructions as string;
}
} else {
// Default to builtin if we can't determine type
@@ -258,7 +256,8 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
const [readableCronExpression, setReadableCronExpression] = useState<string>('');
const [internalValidationError, setInternalValidationError] = useState<string | null>(null);
const handleDeepLinkChange = useCallback((value: string) => {
const handleDeepLinkChange = useCallback(
(value: string) => {
setDeepLinkInput(value);
setInternalValidationError(null);
@@ -268,17 +267,24 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
setParsedRecipe(recipe);
// Auto-populate schedule ID from recipe title if available
if (recipe.title && !scheduleId) {
const cleanId = recipe.title.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
const cleanId = recipe.title
.toLowerCase()
.replace(/[^a-z0-9-]/g, '-')
.replace(/-+/g, '-');
setScheduleId(cleanId);
}
} else {
setParsedRecipe(null);
setInternalValidationError('Invalid deep link format. Please use a goose://bot or goose://recipe link.');
setInternalValidationError(
'Invalid deep link format. Please use a goose://bot or goose://recipe link.'
);
}
} else {
setParsedRecipe(null);
}
}, [scheduleId]);
},
[scheduleId]
);
useEffect(() => {
// Check for pending deep link when modal opens
@@ -610,7 +616,8 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
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..."

View File

@@ -58,16 +58,40 @@ const parseCronExpression = (cron: string) => {
if (dayOfMonth !== '*' && month !== '*' && dayOfWeek === '*') {
return { frequency: 'once' as FrequencyValue, minutes, hours, dayOfMonth, month };
}
if (minutes !== '*' && hours === '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
if (
minutes !== '*' &&
hours === '*' &&
dayOfMonth === '*' &&
month === '*' &&
dayOfWeek === '*'
) {
return { frequency: 'hourly' as FrequencyValue, minutes };
}
if (minutes !== '*' && hours !== '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
if (
minutes !== '*' &&
hours !== '*' &&
dayOfMonth === '*' &&
month === '*' &&
dayOfWeek === '*'
) {
return { frequency: 'daily' as FrequencyValue, minutes, hours };
}
if (minutes !== '*' && hours !== '*' && dayOfMonth === '*' && month === '*' && dayOfWeek !== '*') {
if (
minutes !== '*' &&
hours !== '*' &&
dayOfMonth === '*' &&
month === '*' &&
dayOfWeek !== '*'
) {
return { frequency: 'weekly' as FrequencyValue, minutes, hours, dayOfWeek };
}
if (minutes !== '*' && hours !== '*' && dayOfMonth !== '*' && month === '*' && dayOfWeek === '*') {
if (
minutes !== '*' &&
hours !== '*' &&
dayOfMonth !== '*' &&
month === '*' &&
dayOfWeek === '*'
) {
return { frequency: 'monthly' as FrequencyValue, minutes, hours, dayOfMonth };
}
@@ -107,23 +131,31 @@ export const EditScheduleModal: React.FC<EditScheduleModalProps> = ({
// For 'once', we'd need to reconstruct the date from cron parts
// This is complex, so we'll default to current date/time for now
setSelectedDate(new Date().toISOString().split('T')[0]);
setSelectedTime(`${parsed.hours?.padStart(2, '0')}:${parsed.minutes?.padStart(2, '0')}`);
setSelectedTime(
`${parsed.hours?.padStart(2, '0')}:${parsed.minutes?.padStart(2, '0')}`
);
break;
case 'hourly':
setSelectedMinute(parsed.minutes || '0');
break;
case 'daily':
setSelectedTime(`${parsed.hours?.padStart(2, '0')}:${parsed.minutes?.padStart(2, '0')}`);
setSelectedTime(
`${parsed.hours?.padStart(2, '0')}:${parsed.minutes?.padStart(2, '0')}`
);
break;
case 'weekly':
setSelectedTime(`${parsed.hours?.padStart(2, '0')}:${parsed.minutes?.padStart(2, '0')}`);
setSelectedTime(
`${parsed.hours?.padStart(2, '0')}:${parsed.minutes?.padStart(2, '0')}`
);
if (parsed.dayOfWeek) {
const days = parsed.dayOfWeek.split(',').map(d => d.trim());
const days = parsed.dayOfWeek.split(',').map((d) => d.trim());
setSelectedDaysOfWeek(new Set(days));
}
break;
case 'monthly':
setSelectedTime(`${parsed.hours?.padStart(2, '0')}:${parsed.minutes?.padStart(2, '0')}`);
setSelectedTime(
`${parsed.hours?.padStart(2, '0')}:${parsed.minutes?.padStart(2, '0')}`
);
setSelectedDayOfMonth(parsed.dayOfMonth || '1');
break;
}
@@ -287,7 +319,8 @@ export const EditScheduleModal: React.FC<EditScheduleModalProps> = ({
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..."

View File

@@ -7,7 +7,11 @@ import { ScrollArea } from '../ui/scroll-area';
import MarkdownContent from '../MarkdownContent';
import ToolCallWithResponse from '../ToolCallWithResponse';
import ImagePreview from '../ImagePreview';
import { ToolRequestMessageContent, ToolResponseMessageContent } from '../../types/message';
import {
ToolRequestMessageContent,
ToolResponseMessageContent,
TextContent,
} from '../../types/message';
import { type Message } from '../../types/message';
import { formatMessageTimestamp } from '../../utils/timeUtils';
import { extractImagePaths, removeImagePathsFromText } from '../../utils/imageUtils';
@@ -109,7 +113,7 @@ export const SessionMessages: React.FC<SessionMessagesProps> = ({
.map((message, index) => {
// Extract text content from the message
let textContent = message.content
.filter((c) => c.type === 'text')
.filter((c): c is TextContent => c.type === 'text')
.map((c) => c.text)
.join('\n');

View File

@@ -33,13 +33,13 @@ const SharedSessionView: React.FC<SharedSessionViewProps> = ({
<div className="flex items-center text-sm text-textSubtle mt-1 space-x-5">
<span className="flex items-center">
<Calendar className="w-4 h-4 mr-1" />
{formatMessageTimestamp(session.messages[0]?.created)}
{session ? formatMessageTimestamp(session.messages[0]?.created) : 'Unknown'}
</span>
<span className="flex items-center">
<MessageSquareText className="w-4 h-4 mr-1" />
{session.message_count}
{session ? session.message_count : 0}
</span>
{session.total_tokens !== null && (
{session && session.total_tokens !== null && (
<span className="flex items-center">
<Target className="w-4 h-4 mr-1" />
{session.total_tokens.toLocaleString()}
@@ -49,7 +49,7 @@ const SharedSessionView: React.FC<SharedSessionViewProps> = ({
<div className="flex items-center text-sm text-textSubtle space-x-5">
<span className="flex items-center">
<Folder className="w-4 h-4 mr-1" />
{session.working_dir}
{session ? session.working_dir : 'Unknown'}
</span>
</div>
</div>

View File

@@ -404,8 +404,12 @@ export function OllamaBattleGame({ onComplete, requiredKeys: _ }: OllamaBattleGa
!battleState.processingAction && (
<div className="space-y-2">
{(typeof battleSteps[battleState.currentStep].choices === 'function'
? (battleSteps[battleState.currentStep].choices as (choice: string) => string[])(battleState.lastChoice || '')
: battleSteps[battleState.currentStep].choices as string[]
? (
battleSteps[battleState.currentStep].choices as (
choice: string
) => string[]
)(battleState.lastChoice || '')
: (battleSteps[battleState.currentStep].choices as string[])
)?.map((choice: string) => (
<button
key={choice}

View File

@@ -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<string, string[]>)[provider] || ['API Key'];
const headerText = title || `Setup ${provider}`;
const shouldShowBattle = React.useMemo(() => {
@@ -59,7 +59,7 @@ export function ProviderSetupModal({
) : (
<form onSubmit={handleSubmit}>
<div className="mt-[24px] space-y-4">
{requiredKeys.map((keyName) => (
{requiredKeys.map((keyName: string) => (
<div key={keyName}>
<Input
type={isSecretKey(keyName) ? 'password' : 'text'}

View File

@@ -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,
};

View File

@@ -40,10 +40,10 @@ export async function getActiveProviders(): Promise<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<Record<string, ProviderRespon
supported: true,
description: provider.metadata.description,
models: provider.metadata.models,
config_status: providerRequiredKeys.reduce<Record<string, ConfigDetails>>((acc: Record<string, ConfigDetails>, key: string) => {
config_status: providerRequiredKeys.reduce<Record<string, ConfigDetails>>(
(acc: Record<string, ConfigDetails>, key: string) => {
acc[key] = {
key,
is_set: provider.is_configured,
location: provider.is_configured ? 'config' : undefined,
};
return acc;
}, {}),
},
{}
),
};
});

View File

@@ -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);

View File

@@ -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 */}
<form onSubmit={handleExtensionConfigSubmit}>
<div className="mt-[24px]">
{extension.env_keys?.length > 0 ? (
{extension.env_keys && extension.env_keys.length > 0 ? (
<>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-6">
Please provide the required environment variables for this extension:
</p>
<div className="space-y-4">
{extension.env_keys?.map((envVarName) => (
{extension.env_keys.map((envVarName) => (
<div key={envVarName}>
<label
htmlFor={envVarName}

View File

@@ -40,7 +40,7 @@ export function ConfigureExtensionModal({
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;
@@ -105,13 +105,13 @@ export function ConfigureExtensionModal({
{/* Form */}
<form onSubmit={handleExtensionConfigSubmit}>
<div className="mt-[24px]">
{extension.env_keys?.length > 0 ? (
{extension.env_keys && extension.env_keys.length > 0 ? (
<>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-6">
Please provide the required environment variables for this extension:
</p>
<div className="space-y-4">
{extension.env_keys?.map((envVarName) => (
{extension.env_keys.map((envVarName) => (
<div key={envVarName}>
<label
htmlFor={envVarName}

View File

@@ -97,7 +97,10 @@ export function ManualExtensionModal({ isOpen, onClose, onSubmit }: ManualExtens
resetForm();
} catch (error) {
console.error('Error configuring extension:', error);
toastError({ title: 'Failed to configure extension', traceback: error.message });
toastError({
title: 'Failed to configure extension',
traceback: error instanceof Error ? error.message : String(error),
});
}
};
@@ -142,9 +145,13 @@ export function ManualExtensionModal({ isOpen, onClose, onSubmit }: ManualExtens
<Select
options={typeOptions}
value={typeOptions.find((option) => option.value === formData.type)}
onChange={(option: { value: string; label: string } | null) =>
setFormData({ ...formData, type: option?.value as FullExtensionConfig['type'] })
}
onChange={(newValue: unknown) => {
const option = newValue as { value: string; label: string } | null;
setFormData({
...formData,
type: option?.value as FullExtensionConfig['type'],
});
}}
/>
</div>

View File

@@ -19,7 +19,9 @@ export function AddModelInline() {
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
const [modelName, setModelName] = useState<string>('');
const [filteredModels, setFilteredModels] = useState<{ id: string; name: string; provider: string }[]>([]);
const [filteredModels, setFilteredModels] = useState<
{ id: string; name: string; provider: string }[]
>([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const handleModelSelection = useHandleModelSelection();
@@ -37,7 +39,12 @@ export function AddModelInline() {
model.provider.toLowerCase() === selectedProvider &&
model.name.toLowerCase().includes(modelName.toLowerCase())
)
.slice(0, 5); // Limit suggestions to top 5
.slice(0, 5) // Limit suggestions to top 5
.map((model) => ({
id: String(model.id || ''),
name: model.name,
provider: model.provider,
}));
setFilteredModels(filtered);
setShowSuggestions(filtered.length > 0);
}, [modelName, selectedProvider]);
@@ -76,7 +83,8 @@ export function AddModelInline() {
<Select
options={providerOptions}
value={providerOptions.find((option) => option.value === selectedProvider) || null}
onChange={(option: { value: string | null }) => {
onChange={(newValue: unknown) => {
const option = newValue as { value: string | null } | null;
setSelectedProvider(option?.value || null);
setModelName(''); // Clear model name when provider changes
setFilteredModels([]);

View File

@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { useRecentModels } from './RecentModels';
import { useModel, Model } from './ModelContext';
import { useHandleModelSelection } from './utils';
import type { View } from '@/src/App';
import type { View } from '../../../App';
interface ModelRadioListProps {
renderItem: (props: {

View File

@@ -8,10 +8,13 @@ import { useModel } from './ModelContext';
import { useHandleModelSelection } from './utils';
// Create a mapping from provider name to href
const providerLinks = model_docs_link.reduce((acc, { name, href }) => {
const providerLinks: Record<string, string> = model_docs_link.reduce(
(acc, { name, href }) => {
acc[name] = href;
return acc;
}, {});
},
{} as Record<string, string>
);
export function ProviderButtons() {
const { activeKeys } = useActiveKeys();

View File

@@ -43,7 +43,7 @@ export function useHandleModelSelection() {
toastError({
title: model.name,
msg: `Failed to switch to model`,
traceback: error.message,
traceback: error instanceof Error ? error.message : String(error),
});
}
};

View File

@@ -62,7 +62,7 @@ function BaseProviderCard({
onTakeoff,
showTakeoff,
}: BaseProviderCardProps) {
const numRequiredKeys = required_keys[name]?.length || 0;
const numRequiredKeys = (required_keys as Record<string, string[]>)[name]?.length || 0;
const tooltipText = numRequiredKeys === 1 ? `Add ${name} API Key` : `Add ${name} API Keys`;
return (
@@ -254,7 +254,8 @@ export function BaseProviderGrid({
return (
<div className="grid grid-cols-[repeat(auto-fill,_minmax(140px,_1fr))] gap-3 [&_*]:z-20">
{providers.map((provider) => {
const hasRequiredKeys = required_keys[provider.name]?.length > 0;
const hasRequiredKeys =
(required_keys as Record<string, string[]>)[provider.name]?.length > 0;
return (
<BaseProviderCard
key={provider.id}

View File

@@ -9,7 +9,15 @@ import { useModel } from '../models/ModelContext';
import { Button } from '../../ui/button';
import { toastError, toastSuccess } from '../../../toasts';
function ConfirmationModal({ message, onConfirm, onCancel }) {
function ConfirmationModal({
message,
onConfirm,
onCancel,
}: {
message: string;
onConfirm: () => void;
onCancel: () => void;
}) {
return (
<div className="fixed inset-0 bg-black/20 backdrop-blur-sm z-[9999]">
<div className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[400px] bg-white dark:bg-gray-800 rounded-xl shadow-xl border border-gray-200 dark:border-gray-700">
@@ -43,7 +51,12 @@ export function ConfigureProvidersGrid() {
const [selectedForSetup, setSelectedForSetup] = useState<string | null>(null);
const [modalMode, setModalMode] = useState<'edit' | 'setup' | 'battle'>('setup');
const [isConfirmationOpen, setIsConfirmationOpen] = useState(false);
const [providerToDelete, setProviderToDelete] = useState<{ name: string; id: string; isConfigured: boolean; description: string } | null>(null);
const [providerToDelete, setProviderToDelete] = useState<{
name: string;
id: string;
isConfigured: boolean;
description: string;
} | null>(null);
const { currentModel } = useModel();
const providers = useMemo(() => {
@@ -62,13 +75,23 @@ export function ConfigureProvidersGrid() {
});
}, [activeKeys]);
const handleAddKeys = (provider) => {
const handleAddKeys = (provider: {
id: string;
name: string;
isConfigured: boolean;
description: string;
}) => {
setSelectedForSetup(provider.id);
setModalMode('setup');
setShowSetupModal(true);
};
const handleConfigure = (provider) => {
const handleConfigure = (provider: {
id: string;
name: string;
isConfigured: boolean;
description: string;
}) => {
setSelectedForSetup(provider.id);
setModalMode('edit');
setShowSetupModal(true);
@@ -80,7 +103,7 @@ export function ConfigureProvidersGrid() {
const provider = providers.find((p) => p.id === selectedForSetup)?.name;
if (!provider) return;
const requiredKeys = required_keys[provider];
const requiredKeys = (required_keys as Record<string, string[]>)[provider];
if (!requiredKeys || requiredKeys.length === 0) {
console.error(`No keys found for provider ${provider}`);
return;
@@ -157,12 +180,17 @@ export function ConfigureProvidersGrid() {
toastError({
title: provider,
msg: `Failed to ${providers.find((p) => p.id === selectedForSetup)?.isConfigured ? 'update' : 'add'} configuration`,
traceback: error.message,
traceback: error instanceof Error ? error.message : String(error),
});
}
};
const handleDelete = async (provider) => {
const handleDelete = async (provider: {
id: string;
name: string;
isConfigured: boolean;
description: string;
}) => {
setProviderToDelete(provider);
setIsConfirmationOpen(true);
};
@@ -220,7 +248,7 @@ export function ConfigureProvidersGrid() {
toastError({
title: providerToDelete.name,
msg: 'Failed to delete configuration',
traceback: error.message,
traceback: error instanceof Error ? error.message : String(error),
});
}
setIsConfirmationOpen(false);

View File

@@ -3,7 +3,7 @@ import { Input } from '../../ui/input';
import { Check, Lock } from 'lucide-react';
export default function SessionSharingSection() {
const envBaseUrlShare = window.appConfig.get('GOOSE_BASE_URL_SHARE');
const envBaseUrlShare = window.appConfig.get('GOOSE_BASE_URL_SHARE') as string | undefined;
console.log('envBaseUrlShare', envBaseUrlShare);
// If env is set, force sharing enabled and set the baseUrl accordingly.
@@ -146,7 +146,7 @@ export default function SessionSharingSection() {
placeholder="https://example.com/api"
value={sessionSharingConfig.baseUrl}
disabled={!!envBaseUrlShare}
onChange={envBaseUrlShare ? undefined : handleBaseUrlChange}
onChange={envBaseUrlShare ? () => {} : handleBaseUrlChange}
/>
</div>
{urlError && <p className="text-red-500 text-sm">{urlError}</p>}

View File

@@ -250,7 +250,10 @@ export default function ExtensionsSection({
{deepLinkConfigStateVar && showEnvVarsStateVar && (
<ExtensionModal
title="Add custom extension"
initialData={extensionToFormData({ ...deepLinkConfig, enabled: true })}
initialData={extensionToFormData({
...deepLinkConfig,
enabled: true,
} as FixedExtensionEntry)}
onClose={handleModalClose}
onSubmit={handleAddExtension}
submitLabel="Add Extension"

View File

@@ -29,7 +29,7 @@ export async function extensionApiCall(
};
// for adding the payload is an extensionConfig, for removing payload is just the name
const extensionName = isActivating ? (payload as ExtensionConfig).name : payload as string;
const extensionName = isActivating ? (payload as ExtensionConfig).name : (payload as string);
let toastId;
// Step 1: Show loading toast (only for activation of stdio)
@@ -77,11 +77,13 @@ export async function extensionApiCall(
} catch (error) {
// Final catch-all error handler
toastService.dismiss(toastId);
const msg = error.length < 70 ? error : `Failed to ${action.presentTense} extension`;
const errorMessage = error instanceof Error ? error.message : String(error);
const msg =
errorMessage.length < 70 ? errorMessage : `Failed to ${action.presentTense} extension`;
toastService.error({
title: extensionName,
msg: msg,
traceback: error,
traceback: errorMessage,
});
console.error(`Error in extensionApiCall for ${extensionName}:`, error);
throw error;
@@ -95,7 +97,7 @@ function handleErrorResponse(
response: Response,
extensionName: string,
action: { type: string; verb: string },
toastId: string
toastId: string | number | undefined
): never {
const errorMsg = `Server returned ${response.status}: ${response.statusText}`;
console.error(errorMsg);
@@ -150,7 +152,7 @@ export async function addToAgent(
return await extensionApiCall('/extensions/add', extension, options);
} catch (error) {
// Check if this is a 428 error and make the message more descriptive
if (error.message && error.message.includes('428')) {
if (error instanceof Error && error.message && error.message.includes('428')) {
const enhancedError = new Error(
'Failed to add extension. Goose Agent was still starting up. Please try again.'
);

View File

@@ -63,10 +63,10 @@ export async function syncBundledExtensions(
description: bundledExt.description,
type: bundledExt.type,
timeout: bundledExt.timeout,
cmd: bundledExt.cmd,
args: bundledExt.args,
cmd: bundledExt.cmd || '',
args: bundledExt.args || [],
envs: bundledExt.envs,
env_keys: bundledExt.env_keys,
env_keys: bundledExt.env_keys || [],
bundled: true,
};
break;
@@ -76,7 +76,7 @@ export async function syncBundledExtensions(
description: bundledExt.description,
type: bundledExt.type,
timeout: bundledExt.timeout,
uri: bundledExt.uri,
uri: bundledExt.uri || '',
bundled: true,
};
}

View File

@@ -122,8 +122,8 @@ export async function addExtensionFromDeepLink(
const remoteUrl = parsedUrl.searchParams.get('url');
const config = remoteUrl
? getSseConfig(remoteUrl, name, description, timeout)
: getStdioConfig(cmd!, parsedUrl, name, description, timeout);
? getSseConfig(remoteUrl, name, description || '', timeout)
: getStdioConfig(cmd!, parsedUrl, name, description || '', timeout);
// Check if extension requires env vars and go to settings if so
if (config.envs && Object.keys(config.envs).length > 0) {

View File

@@ -25,7 +25,7 @@ async function retryWithBackoff<T>(fn: () => Promise<T>, options: RetryOptions =
const { retries = 3, delayMs = 1000, backoffFactor = 1.5, shouldRetry = () => true } = options;
let attempt = 0;
let lastError: ExtensionError;
let lastError: ExtensionError = new Error('Unknown error');
while (attempt <= retries) {
try {
@@ -100,7 +100,7 @@ export async function addToAgentOnStartup({
retries: 3,
delayMs: 1000,
shouldRetry: (error: ExtensionError) =>
error.message &&
!!error.message &&
(error.message.includes('428') ||
error.message.includes('Precondition Required') ||
error.message.includes('Agent is not initialized')),
@@ -110,7 +110,7 @@ export async function addToAgentOnStartup({
toastService.error({
title: extensionConfig.name,
msg: 'Extension failed to start and will be disabled.',
traceback: finalError as Error,
traceback: finalError instanceof Error ? finalError.message : String(finalError),
});
try {

View File

@@ -44,7 +44,8 @@ export default function ExtensionInfoFields({
<label className="text-sm font-medium mb-2 block text-textStandard">Type</label>
<Select
value={{ value: type, label: type.toUpperCase() }}
onChange={(option: { value: string; label: string } | null) => {
onChange={(newValue: unknown) => {
const option = newValue as { value: string; label: string } | null;
if (option) {
onChange('type', option.value);
}

View File

@@ -98,8 +98,8 @@ export default function ExtensionModal({
const isConfigValid = () => {
return (
(formData.type === 'stdio' && formData.cmd && formData.cmd.trim() !== '') ||
(formData.type === 'sse' && formData.endpoint && formData.endpoint.trim() !== '')
(formData.type === 'stdio' && !!formData.cmd && formData.cmd.trim() !== '') ||
(formData.type === 'sse' && !!formData.endpoint && formData.endpoint.trim() !== '')
);
};
@@ -263,7 +263,7 @@ export default function ExtensionModal({
/>
<div className="mb-4" />
<ExtensionTimeoutField
timeout={formData.timeout}
timeout={formData.timeout || 300}
onChange={(key, value) => setFormData({ ...formData, [key]: value })}
submitAttempted={submitAttempted}
/>

View File

@@ -9,6 +9,7 @@ interface ExtensionListProps {
onToggle: (extension: FixedExtensionEntry) => Promise<boolean | void> | void;
onConfigure?: (extension: FixedExtensionEntry) => void;
isStatic?: boolean;
disableConfiguration?: boolean;
}
export default function ExtensionList({
@@ -16,6 +17,7 @@ export default function ExtensionList({
onToggle,
onConfigure,
isStatic,
disableConfiguration: _disableConfiguration,
}: ExtensionListProps) {
return (
<div className="grid grid-cols-2 gap-2 mb-2">

View File

@@ -76,14 +76,14 @@ export function extensionToFormData(extension: FixedExtensionEntry): ExtensionFo
}
return {
name: extension.name,
name: extension.name || '',
description:
extension.type === 'stdio' || extension.type === 'sse' ? extension.description : undefined,
type: extension.type,
extension.type === 'stdio' || extension.type === 'sse' ? extension.description || '' : '',
type: extension.type === 'frontend' ? 'stdio' : extension.type,
cmd: extension.type === 'stdio' ? combineCmdAndArgs(extension.cmd, extension.args) : undefined,
endpoint: extension.type === 'sse' ? extension.uri : undefined,
enabled: extension.enabled,
timeout: 'timeout' in extension ? extension.timeout : undefined,
timeout: 'timeout' in extension ? (extension.timeout ?? undefined) : undefined,
envVars,
};
}
@@ -94,7 +94,7 @@ export function createExtensionConfig(formData: ExtensionFormData): ExtensionCon
if (formData.type === 'stdio') {
// we put the cmd + args all in the form cmd field but need to split out into cmd + args
const { cmd, args } = splitCmdAndArgs(formData.cmd);
const { cmd, args } = splitCmdAndArgs(formData.cmd || '');
return {
type: 'stdio',
@@ -111,7 +111,7 @@ export function createExtensionConfig(formData: ExtensionFormData): ExtensionCon
name: formData.name,
description: formData.description,
timeout: formData.timeout,
uri: formData.endpoint,
uri: formData.endpoint || '',
...(env_keys.length > 0 ? { env_keys } : {}),
};
} else {

View File

@@ -39,7 +39,7 @@ export function ConfigureApproveMode({
setIsSubmitting(true);
try {
handleModeChange(approveMode);
handleModeChange(approveMode || '');
onClose();
} catch (error) {
console.error('Error configuring goose mode:', error);
@@ -68,8 +68,10 @@ export function ConfigureApproveMode({
key={mode.key}
mode={mode}
showDescription={true}
currentMode={approveMode}
currentMode={approveMode || ''}
isApproveModeConfigure={true}
parentView={'settings' as const}
setView={() => {}} // No-op since we're in configure mode
handleModeChange={(newMode) => {
setApproveMode(newMode);
}}

View File

@@ -1,6 +1,6 @@
import { initializeAgent } from '../../../agent';
import { toastError, toastSuccess } from '../../../toasts';
import { ProviderDetails } from '@/src/api';
import { ProviderDetails } from '../../../api';
import Model, { getProviderMetadata } from './modelInterface';
import { ProviderMetadata } from '../../../api';
@@ -37,7 +37,7 @@ export async function changeModel({ model, writeToConfig }: changeModelProps) {
toastError({
title: CHANGE_MODEL_ERROR_TITLE,
msg: SWITCH_MODEL_AGENT_ERROR_MSG,
traceback: error,
traceback: error instanceof Error ? error.message : String(error),
});
// don't write to config
return;
@@ -51,7 +51,7 @@ export async function changeModel({ model, writeToConfig }: changeModelProps) {
toastError({
title: CHANGE_MODEL_ERROR_TITLE,
msg: CONFIG_UPDATE_ERROR_MSG,
traceback: error,
traceback: error instanceof Error ? error.message : String(error),
});
// agent and config will be out of sync at this point
// TODO: reset agent to use current config settings
@@ -92,7 +92,7 @@ export async function getCurrentModelAndProvider({
}
export async function getFallbackModelAndProvider(
writeToConfig: (key: string, value: unknown, is_secret: boolean) => Promise<void>
writeToConfig?: (key: string, value: unknown, is_secret: boolean) => Promise<void>
) {
const provider = window.appConfig.get('GOOSE_DEFAULT_PROVIDER');
const model = window.appConfig.get('GOOSE_DEFAULT_MODEL');
@@ -125,7 +125,7 @@ export async function getCurrentModelAndProviderForDisplay({
let metadata: ProviderMetadata;
try {
metadata = await getProviderMetadata(gooseProvider, getProviders);
metadata = await getProviderMetadata(String(gooseProvider), getProviders);
} catch (error) {
return { model: gooseModel, provider: gooseProvider };
}

View File

@@ -52,7 +52,7 @@ export function BaseModelsList({
);
// no matches so just create a model object (maybe user updated config.yaml from CLI usage, manual editing etc)
if (!match) {
currentModel = { name: result.model, provider: result.provider };
currentModel = { name: String(result.model), provider: String(result.provider) };
} else {
currentModel = match;
}
@@ -109,9 +109,9 @@ export function BaseModelsList({
writeToConfig: upsert,
});
const currentModel = modelList.find(
(m) => m.name === result.model && m.provider === result.provider
) || { name: result.model, provider: result.provider };
const currentModel =
modelList.find((m) => m.name === result.model && m.provider === result.provider) ||
({ name: String(result.model), provider: String(result.provider) } as Model);
setSelectedModel(currentModel);
} catch (secondError) {
@@ -136,10 +136,11 @@ export function BaseModelsList({
{modelList.map((model) =>
renderItem({
model,
isSelected:
isSelected: !!(
selectedModel &&
selectedModel.name === model.name &&
selectedModel.provider === model.provider,
selectedModel.provider === model.provider
),
onSelect: () => handleRadioChange(model),
})
)}

View File

@@ -12,7 +12,17 @@ import type { View } from '../../../../App';
import Model, { getProviderMetadata } from '../modelInterface';
import { useModel } from '../../../settings/models/ModelContext';
const ModalButtons = ({ onSubmit, onCancel, _isValid: _, _validationErrors: __ }) => (
const ModalButtons = ({
onSubmit,
onCancel,
_isValid: _,
_validationErrors: __,
}: {
onSubmit: () => void;
onCancel: () => void;
_isValid: boolean;
_validationErrors: { provider: string; model: string };
}) => (
<div>
<Button
type="submit"
@@ -41,7 +51,9 @@ export const AddModelModal = ({ onClose, setView }: AddModelModalProps) => {
const { getProviders, upsert } = useConfig();
const { switchModel } = useModel();
const [providerOptions, setProviderOptions] = useState<{ value: string; label: string }[]>([]);
const [modelOptions, setModelOptions] = useState<{ options: { value: string; label: string; provider: string }[] }[]>([]);
const [modelOptions, setModelOptions] = useState<
{ options: { value: string; label: string; provider: string }[] }[]
>([]);
const [provider, setProvider] = useState<string | null>(null);
const [model, setModel] = useState<string>('');
const [isCustomModel, setIsCustomModel] = useState(false);
@@ -80,7 +92,7 @@ export const AddModelModal = ({ onClose, setView }: AddModelModalProps) => {
const isFormValid = validateForm();
if (isFormValid) {
const providerMetaData = await getProviderMetadata(provider, getProviders);
const providerMetaData = await getProviderMetadata(provider || '', getProviders);
const providerDisplayName = providerMetaData.display_name;
const modelObj = { name: model, provider: provider, subtext: providerDisplayName } as Model;
@@ -122,7 +134,9 @@ export const AddModelModal = ({ onClose, setView }: AddModelModalProps) => {
]);
// Format model options by provider
const formattedModelOptions = [];
const formattedModelOptions: {
options: { value: string; label: string; provider: string }[];
}[] = [];
activeProviders.forEach(({ metadata, name }) => {
if (metadata.known_models && metadata.known_models.length > 0) {
formattedModelOptions.push({
@@ -158,7 +172,8 @@ export const AddModelModal = ({ onClose, setView }: AddModelModalProps) => {
: [];
// Handle model selection change
const handleModelChange = (selectedOption) => {
const handleModelChange = (newValue: unknown) => {
const selectedOption = newValue as { value: string; label: string; provider: string } | null;
if (selectedOption?.value === 'custom') {
setIsCustomModel(true);
setModel('');
@@ -169,7 +184,8 @@ export const AddModelModal = ({ onClose, setView }: AddModelModalProps) => {
};
// Store the original model options in state, initialized from modelOptions
const [originalModelOptions, setOriginalModelOptions] = useState<{ options: { value: string; label: string; provider: string }[] }[]>(modelOptions);
const [originalModelOptions, setOriginalModelOptions] =
useState<{ options: { value: string; label: string; provider: string }[] }[]>(modelOptions);
const handleInputChange = (inputValue: string) => {
if (!provider) return;
@@ -221,8 +237,8 @@ export const AddModelModal = ({ onClose, setView }: AddModelModalProps) => {
<ModalButtons
onSubmit={onSubmit}
onCancel={onClose}
isValid={isValid}
validationErrors={validationErrors}
_isValid={isValid}
_validationErrors={validationErrors}
/>
}
>
@@ -252,7 +268,8 @@ export const AddModelModal = ({ onClose, setView }: AddModelModalProps) => {
<Select
options={providerOptions}
value={providerOptions.find((option) => option.value === provider) || null}
onChange={(option) => {
onChange={(newValue: unknown) => {
const option = newValue as { value: string; label: string } | null;
if (option?.value === 'configure_providers') {
// Navigate to ConfigureProviders view
setView('ConfigureProviders');

View File

@@ -102,7 +102,7 @@ export default memo(function ProviderGrid({
providers={providers}
isOnboarding={isOnboarding}
refreshProviders={refreshProviders}
onProviderLaunch={onProviderLaunch}
onProviderLaunch={onProviderLaunch || (() => {})}
/>
<ProviderConfigurationModal />
</ProviderModalProvider>

View File

@@ -13,19 +13,23 @@ import { useConfig } from '../../../ConfigContext';
import { AlertTriangle } from 'lucide-react';
import { getCurrentModelAndProvider } from '../../models'; // Import the utility
const customSubmitHandlerMap = {
interface FormValues {
[key: string]: string | number | boolean | null;
}
const customSubmitHandlerMap: Record<string, unknown> = {
provider_name: OllamaSubmitHandler, // example
};
const customFormsMap = {
const customFormsMap: Record<string, unknown> = {
provider_name: OllamaForm, // example
};
export default function ProviderConfigurationModal() {
const [validationErrors, setValidationErrors] = useState({});
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});
const { upsert, remove, read } = useConfig(); // Add read to the destructured values
const { isOpen, currentProvider, modalProps, closeModal } = useProviderModal();
const [configValues, setConfigValues] = useState({});
const [configValues, setConfigValues] = useState<Record<string, string>>({});
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
const [isActiveProvider, setIsActiveProvider] = useState(false); // New state for tracking active provider
@@ -53,10 +57,14 @@ export default function ProviderConfigurationModal() {
: 'This will permanently delete the current provider configuration.'
: `Add your API key(s) for this provider to integrate into Goose`;
const SubmitHandler = customSubmitHandlerMap[currentProvider.name] || DefaultSubmitHandler;
const FormComponent = customFormsMap[currentProvider.name] || DefaultProviderSetupForm;
const SubmitHandler =
(customSubmitHandlerMap[currentProvider.name] as typeof DefaultSubmitHandler) ||
DefaultSubmitHandler;
const FormComponent =
(customFormsMap[currentProvider.name] as typeof DefaultProviderSetupForm) ||
DefaultProviderSetupForm;
const handleSubmitForm = async (e) => {
const handleSubmitForm = async (e: React.FormEvent) => {
e.preventDefault();
console.log('Form submitted for:', currentProvider.name);
@@ -65,7 +73,7 @@ export default function ProviderConfigurationModal() {
// Validation logic
const parameters = currentProvider.metadata.config_keys || [];
const errors = {};
const errors: Record<string, string> = {};
// Check required fields
parameters.forEach((parameter) => {
@@ -94,7 +102,7 @@ export default function ProviderConfigurationModal() {
// Call onSubmit callback if provided (from modal props)
if (modalProps.onSubmit) {
modalProps.onSubmit(configValues);
modalProps.onSubmit(configValues as FormValues);
}
} catch (error) {
console.error('Failed to save configuration:', error);
@@ -156,7 +164,7 @@ export default function ProviderConfigurationModal() {
// Call onDelete callback if provided
// This should trigger the refreshProviders function
if (modalProps.onDelete) {
modalProps.onDelete(currentProvider.name);
modalProps.onDelete(currentProvider.name as unknown as FormValues);
}
// Reset the delete confirmation state before closing

View File

@@ -8,7 +8,7 @@ import OpenRouterLogo from './icons/openrouter@3x.png';
import DefaultLogo from './icons/default@3x.png';
// Map provider names to their logos
const providerLogos = {
const providerLogos: Record<string, string> = {
openai: OpenAILogo,
anthropic: AnthropicLogo,
google: GoogleLogo,

View File

@@ -1,29 +1,7 @@
import React, { useEffect, useMemo, useState, useCallback } from 'react';
import { Input } from '../../../../../ui/input';
import { useConfig } from '../../../../../ConfigContext'; // Adjust this import path as needed
interface ConfigParameter {
name: string;
required: boolean;
secret?: boolean;
default?: string | number | boolean | null;
}
interface ProviderMetadata {
config_keys?: ConfigParameter[];
display_name?: string;
description?: string;
known_models?: string[];
default_model?: string;
[key: string]: string | string[] | ConfigParameter[] | undefined;
}
interface Provider {
metadata: ProviderMetadata;
name: string;
is_configured: boolean;
[key: string]: string | boolean | ProviderMetadata;
}
import { ProviderDetails, ConfigKey } from '../../../../../../api';
interface ValidationErrors {
[key: string]: string;
@@ -32,7 +10,7 @@ interface ValidationErrors {
interface DefaultProviderSetupFormProps {
configValues: Record<string, string>;
setConfigValues: React.Dispatch<React.SetStateAction<Record<string, string>>>;
provider: Provider;
provider: ProviderDetails;
validationErrors: ValidationErrors;
}
@@ -108,7 +86,7 @@ export default function DefaultProviderSetupForm({
}, [parameters]);
// Helper function to generate appropriate placeholder text
const getPlaceholder = (parameter: ConfigParameter): string => {
const getPlaceholder = (parameter: ConfigKey): string => {
// If default is defined and not null, show it
if (parameter.default !== undefined && parameter.default !== null) {
return `Default: ${parameter.default}`;

View File

@@ -1,18 +1,26 @@
import { PROVIDER_REGISTRY } from '../../../ProviderRegistry';
import { Input } from '../../../../../ui/input';
import { useState, useEffect, useCallback } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import { RefreshCw } from 'lucide-react';
import CustomRadio from '../../../../../ui/CustomRadio';
export default function OllamaForm({ configValues, setConfigValues, provider }) {
export default function OllamaForm({
configValues,
setConfigValues,
provider,
}: {
configValues: Record<string, string>;
setConfigValues: React.Dispatch<React.SetStateAction<Record<string, string>>>;
provider: { name: string; [key: string]: unknown };
}) {
const providerEntry = PROVIDER_REGISTRY.find((p) => p.name === provider.name);
const parameters = providerEntry?.details?.parameters || [];
const [isCheckingLocal, setIsCheckingLocal] = useState(false);
const [isLocalAvailable, setIsLocalAvailable] = useState(false);
const handleConnectionTypeChange = useCallback(
(value) => {
(value: string) => {
setConfigValues((prev) => ({
...prev,
connection_type: value,
@@ -22,7 +30,7 @@ export default function OllamaForm({ configValues, setConfigValues, provider })
);
// Function to handle input changes and auto-select/deselect the host radio
const handleInputChange = (paramName, value) => {
const handleInputChange = (paramName: string, value: string) => {
// Update the parameter value
setConfigValues((prev) => ({
...prev,

View File

@@ -2,10 +2,24 @@
* Standalone function to submit provider configuration
* Useful for components that don't want to use the hook
*/
export const DefaultSubmitHandler = async (upsertFn, provider, configValues) => {
export const DefaultSubmitHandler = async (
upsertFn: (key: string, value: unknown, isSecret: boolean) => Promise<void>,
provider: {
metadata: {
config_keys?: Array<{
name: string;
required?: boolean;
default?: unknown;
secret?: boolean;
}>;
};
},
configValues: Record<string, unknown>
) => {
const parameters = provider.metadata.config_keys || [];
const upsertPromises = parameters.map((parameter) => {
const upsertPromises = parameters.map(
(parameter: { name: string; required?: boolean; default?: unknown; secret?: boolean }) => {
// Skip parameters that don't have a value and aren't required
if (!configValues[parameter.name] && !parameter.required) {
return Promise.resolve();
@@ -13,7 +27,9 @@ export const DefaultSubmitHandler = async (upsertFn, provider, configValues) =>
// For required parameters with no value, use the default if available
const value =
configValues[parameter.name] !== undefined ? configValues[parameter.name] : parameter.default;
configValues[parameter.name] !== undefined
? configValues[parameter.name]
: parameter.default;
// Skip if there's still no value
if (value === undefined || value === null) {
@@ -28,7 +44,8 @@ export const DefaultSubmitHandler = async (upsertFn, provider, configValues) =>
// Pass the is_secret flag from the parameter definition
return upsertFn(configKey, value, isSecret);
});
}
);
// Wait for all upsert operations to complete
return Promise.all(upsertPromises);

View File

@@ -1,4 +1,4 @@
export default function OllamaSubmitHandler(configValues) {
export default function OllamaSubmitHandler(configValues: Record<string, unknown>) {
// Log each field value individually for clarity
console.log('Ollama field values:');
Object.entries(configValues).forEach(([key, value]) => {

View File

@@ -45,7 +45,7 @@ export default function CardContainer({
}`}
onClick={!grayedOut ? onClick : undefined}
style={{
cursor: !grayedOut && onClick ? 'pointer' : 'default',
cursor: !grayedOut ? 'pointer' : 'default',
}}
>
{!grayedOut && <GlowingRing />}

View File

@@ -1,4 +1,3 @@
// Functions for string / string-based element creation (e.g. tooltips for each provider, descriptions, etc)
export function OllamaNotConfiguredTooltipMessage() {
return (

View File

@@ -10,12 +10,14 @@ export default function SessionSharingSection() {
// If env is set, force sharing enabled and set the baseUrl accordingly.
const [sessionSharingConfig, setSessionSharingConfig] = useState({
enabled: envBaseUrlShare ? true : false,
baseUrl: envBaseUrlShare || '',
baseUrl: typeof envBaseUrlShare === 'string' ? envBaseUrlShare : '',
});
const [urlError, setUrlError] = useState('');
// isUrlConfigured is true if the user has configured a baseUrl and it is valid.
const isUrlConfigured =
!envBaseUrlShare && sessionSharingConfig.enabled && isValidUrl(sessionSharingConfig.baseUrl);
!envBaseUrlShare &&
sessionSharingConfig.enabled &&
isValidUrl(String(sessionSharingConfig.baseUrl));
// Only load saved config from localStorage if the env variable is not provided.
useEffect(() => {
@@ -23,7 +25,7 @@ export default function SessionSharingSection() {
// If env variable is set, save the forced configuration to localStorage
const forcedConfig = {
enabled: true,
baseUrl: envBaseUrlShare,
baseUrl: typeof envBaseUrlShare === 'string' ? envBaseUrlShare : '',
};
localStorage.setItem('session_sharing_config', JSON.stringify(forcedConfig));
} else {
@@ -113,7 +115,7 @@ export default function SessionSharingSection() {
) : (
<Switch
checked={sessionSharingConfig.enabled}
disabled={envBaseUrlShare}
disabled={!!envBaseUrlShare}
onCheckedChange={toggleSharing}
variant="mono"
/>
@@ -139,7 +141,7 @@ export default function SessionSharingSection() {
placeholder="https://example.com/api"
value={sessionSharingConfig.baseUrl}
disabled={!!envBaseUrlShare}
onChange={envBaseUrlShare ? undefined : handleBaseUrlChange}
{...(envBaseUrlShare ? {} : { onChange: handleBaseUrlChange })}
/>
</div>
{urlError && <p className="text-red-500 text-sm">{urlError}</p>}

View File

@@ -1,4 +1,3 @@
export default function Box({ size }: { size: number }) {
return (
<svg

View File

@@ -23,7 +23,6 @@ export function ConfirmationModal({
<BaseModal
isOpen={isOpen}
title={title}
onClose={onCancel}
actions={
<>
<button

View File

@@ -1,4 +1,3 @@
/**
* CustomRadio - A reusable radio button component with dark mode support
* @param {Object} props - Component props
@@ -25,6 +24,17 @@ const CustomRadio = ({
secondaryLabel = null,
rightContent = null,
className = '',
}: {
id: string;
name: string;
value: string;
checked: boolean;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
disabled?: boolean;
label?: React.ReactNode;
secondaryLabel?: React.ReactNode;
rightContent?: React.ReactNode;
className?: string;
}) => {
return (
<label

View File

@@ -136,6 +136,9 @@ export function DeepLinkModal({ recipeConfig: initialRecipeConfig, onClose }: De
onClick={() => {
// Open the deep link with the current bot config
const currentConfig = {
id: 'deeplink-recipe',
name: 'DeepLink Recipe',
description: 'Recipe from deep link',
...recipeConfig,
instructions,
activities,

View File

@@ -1,6 +1,7 @@
import React from 'react';
import ReactSelect from 'react-select';
export const Select = (props) => {
export const Select = (props: React.ComponentProps<typeof ReactSelect>) => {
return (
<ReactSelect
{...props}

View File

@@ -1,4 +1,3 @@
export default function Send({ size }: { size: number }) {
return (
<svg
@@ -12,7 +11,7 @@ export default function Send({ size }: { size: number }) {
<path
d="M22 12.5L2 4.5L4 12.5L2 20.5L22 12.5ZM5.81 13.5H14.11L4.88 17.19L5.81 13.5ZM14.11 11.5H5.81L4.89 7.81L14.11 11.5Z"
fill="#7A7EFB"
dark:fill="#4A56E2"
className="dark:fill-[#4A56E2]"
/>
</svg>
);

View File

@@ -1,4 +1,3 @@
interface StopProps {
size?: number;
}

View File

@@ -1,4 +1,3 @@
export default function VertDots({ size }: { size: number }) {
return (
<svg

View File

@@ -1,4 +1,3 @@
export default function X({ size }: { size: number }) {
return (
<svg
@@ -13,7 +12,7 @@ export default function X({ size }: { size: number }) {
clipRule="evenodd"
d="M5.97237 6.00001L3.82593 3.85356L4.53303 3.14645L6.67948 5.2929L8.82593 3.14645L9.53303 3.85356L7.38659 6.00001L9.53303 8.14645L8.82593 8.85356L6.67948 6.70711L4.53303 8.85356L3.82593 8.14645L5.97237 6.00001Z"
fill="black"
dark:fill="white"
className="dark:fill-white"
fillOpacity="0.6"
/>
</svg>

View File

@@ -1,4 +1,3 @@
export const BotIcon = () => {
return (
<svg

View File

@@ -1,10 +1,13 @@
// Helper to construct API endpoints
export const getApiUrl = (endpoint: string): string => {
const baseUrl = window.appConfig.get('GOOSE_API_HOST') + ':' + window.appConfig.get('GOOSE_PORT');
const baseUrl =
String(window.appConfig.get('GOOSE_API_HOST') || '') +
':' +
String(window.appConfig.get('GOOSE_PORT') || '');
const cleanEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
return `${baseUrl}${cleanEndpoint}`;
};
export const getSecretKey = (): string => {
return window.appConfig.get('secretKey');
return String(window.appConfig.get('secretKey') || '');
};

Some files were not shown because too many files have changed in this diff Show More