Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1184cd2
Phase 1: Remove unused React imports from safe components
zanesq May 28, 2025
fe9c098
Phase 2: Fix unused variables and parameters
zanesq May 28, 2025
3a1d233
Phase 3: Fix null/undefined access with safe type assertions
zanesq May 28, 2025
e16d6bf
Phase 4: Fix complex type issues and IPC handler compatibility
zanesq May 28, 2025
3837c1c
Phase 5: Remove remaining 51 unused React imports
zanesq May 28, 2025
bd435a8
Phase 6: Implement proper error handling without unknown/any types
zanesq May 28, 2025
eda4a45
Phase 7: Fix implicit any types
zanesq May 28, 2025
8eefc54
Phase 8: Fix property access errors (TS2339)
zanesq May 29, 2025
08e3994
Fix linting issues from Phase 8
zanesq May 29, 2025
0883b8f
Fix all remaining linting issues
zanesq May 29, 2025
0878b1b
Merge branch 'main' of github.com:block/goose into fix/typescript-err…
zanesq May 29, 2025
ff7c4db
Phase 9: Fix argument type errors (TS2345) - TypeScript error reducti…
zanesq May 30, 2025
ec2a434
ts version compatible fix
zanesq May 30, 2025
76b26b1
pin ts to work with ci and local
zanesq May 30, 2025
2309000
merged in main
zanesq May 30, 2025
ded6a30
Merge branch 'main' of github.com:block/goose into fix/typescript-err…
zanesq May 30, 2025
f7d18c1
Merge branch 'main' into fix/typescript-errors-phase8-property-access
zanesq May 30, 2025
6b9de58
Merge branch 'main' into fix/typescript-errors-phase8-property-access
zanesq May 30, 2025
ee0a216
fix upstream typescript errors
zanesq May 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,968 changes: 1,053 additions & 915 deletions ui/desktop/package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion ui/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
],
Expand Down
49 changes: 33 additions & 16 deletions ui/desktop/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -52,20 +54,20 @@ export type ViewOptions = {
extensionId?: string;
showEnvVars?: boolean;
deepLinkConfig?: ExtensionConfig;
// Session view options

// Session view options
resumedSession?: SessionDetails;
sessionDetails?: SessionDetails;
error?: string;
shareToken?: string;
baseUrl?: string;

// Recipe editor options
config?: unknown;

// Permission view options
parentView?: View;

// Generic options
[key: string]: unknown;
};
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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);
Expand All @@ -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);
};
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand Down Expand Up @@ -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')}
Expand All @@ -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) {
Expand All @@ -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' && (
Expand Down
1 change: 0 additions & 1 deletion ui/desktop/src/components/AgentHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

interface AgentHeaderProps {
title: string;
profileInfo?: string;
Expand Down
62 changes: 31 additions & 31 deletions ui/desktop/src/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -149,19 +145,21 @@ export default function ChatInput({

// Debounced function to update actual value
const debouncedSetValue = useMemo(
() => debounce((value: string) => {
setValue(value);
}, 150),
() =>
debounce((value: string) => {
setValue(value);
}, 150),
[setValue]
);

// Debounced autosize function
const debouncedAutosize = useMemo(
() => debounce((element: HTMLTextAreaElement) => {
element.style.height = '0px'; // Reset height
const scrollHeight = element.scrollHeight;
element.style.height = Math.min(scrollHeight, maxHeight) + 'px';
}, 150),
() =>
debounce((element: HTMLTextAreaElement) => {
element.style.height = '0px'; // Reset height
const scrollHeight = element.scrollHeight;
element.style.height = Math.min(scrollHeight, maxHeight) + 'px';
}, 150),
[maxHeight]
);

Expand All @@ -179,10 +177,10 @@ 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;

// Check if adding these images would exceed the limit
if (pastedImages.length + imageFiles.length > MAX_IMAGES_PER_MESSAGE) {
// Show error message to user
Expand All @@ -192,20 +190,20 @@ 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;
}

evt.preventDefault();

for (const file of imageFiles) {
// Check individual file size before processing
if (file.size > MAX_IMAGE_SIZE_MB * 1024 * 1024) {
Expand All @@ -216,18 +214,18 @@ 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;
}

const reader = new FileReader();
reader.onload = async (e) => {
const dataUrl = e.target?.result as string;
Expand Down Expand Up @@ -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('');
Expand Down Expand Up @@ -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>
Expand Down
30 changes: 19 additions & 11 deletions ui/desktop/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
ToolResponseMessageContent,
ToolConfirmationRequestMessageContent,
getTextContent,
TextContent,
} from '../types/message';

export interface ChatType {
Expand Down Expand Up @@ -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
);

Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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}
/>
) : (
Expand Down
Loading
Loading