Fix a few ui edge cases - refresh occasionally crashing, chat loader over text and chat input height returning to auto (#3469)

This commit is contained in:
Zane
2025-07-16 19:06:56 -07:00
committed by GitHub
parent cd940dd6f7
commit 33c082cd5b
7 changed files with 85 additions and 13 deletions

View File

@@ -10,7 +10,7 @@
"license": {
"name": "Apache-2.0"
},
"version": "1.0.36"
"version": "1.1.0"
},
"paths": {
"/agent/tools": {

View File

@@ -446,9 +446,6 @@ function BaseChatContent({
{/* Custom content after messages */}
{renderAfterMessages && renderAfterMessages()}
{/* Bottom padding to make space for the loading indicator */}
<div className="block h-20" />
</ScrollArea>
{/* Fixed loading indicator at bottom left of chat container */}

View File

@@ -427,7 +427,6 @@ export default function ChatInput({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [numTokens, toolCount, tokenLimit, isTokenLimitLoaded, addAlert, clearAlerts]);
const minHeight = '1rem';
const maxHeight = 10 * 24;
// Debounced function to update actual value
@@ -456,6 +455,13 @@ export default function ChatInput({
}
}, [debouncedAutosize, displayValue]);
// Reset textarea height when displayValue is empty
useEffect(() => {
if (textAreaRef.current && displayValue === '') {
textAreaRef.current.style.height = 'auto';
}
}, [displayValue]);
const handleChange = (evt: React.ChangeEvent<HTMLTextAreaElement>) => {
const val = evt.target.value;
const cursorPosition = evt.target.selectionStart;
@@ -910,7 +916,6 @@ export default function ChatInput({
ref={textAreaRef}
rows={1}
style={{
minHeight: `${minHeight}px`,
maxHeight: `${maxHeight}px`,
overflowY: 'auto',
opacity: isRecording ? 0 : 1,

View File

@@ -199,7 +199,7 @@ export default function Pair({
onMessageStreamFinish={handleMessageStreamFinish}
renderBeforeMessages={renderBeforeMessages}
customChatInputProps={customChatInputProps}
contentClassName={cn('pr-1', (isMobile || sidebarState === 'collapsed') && 'pt-11')} // Use dynamic content class with mobile margin and sidebar state
contentClassName={cn('pr-1 pb-10', (isMobile || sidebarState === 'collapsed') && 'pt-11')} // Use dynamic content class with mobile margin and sidebar state
showPopularTopics={!isTransitioningFromHub} // Don't show popular topics while transitioning from Hub
suppressEmptyState={isTransitioningFromHub} // Suppress all empty state content while transitioning from Hub
/>

View File

@@ -23,6 +23,7 @@ export function SessionInsights() {
const [error, setError] = useState<string | null>(null);
const [recentSessions, setRecentSessions] = useState<Session[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isLoadingSessions, setIsLoadingSessions] = useState(true);
// const [recentProjects, setRecentProjects] = useState<ProjectMetadata[]>([]);
const navigate = useNavigate();
@@ -67,7 +68,8 @@ export function SessionInsights() {
setRecentSessions(sessions.slice(0, 3));
} catch (error) {
console.error('Failed to load recent sessions:', error);
// Don't set loading to false here since insights is the primary data
} finally {
setIsLoadingSessions(false);
}
};
@@ -400,7 +402,32 @@ export function SessionInsights() {
</Button>
</div>
<div className="space-y-1 min-h-[96px] transition-all duration-300 ease-in-out">
{recentSessions.length > 0 ? (
{isLoadingSessions ? (
// Show skeleton while sessions are loading
<>
<div className="flex items-center justify-between py-1 px-2">
<div className="flex items-center space-x-2">
<Skeleton className="h-4 w-4 rounded-sm" />
<Skeleton className="h-4 w-48" />
</div>
<Skeleton className="h-4 w-16" />
</div>
<div className="flex items-center justify-between py-1 px-2">
<div className="flex items-center space-x-2">
<Skeleton className="h-4 w-4 rounded-sm" />
<Skeleton className="h-4 w-40" />
</div>
<Skeleton className="h-4 w-16" />
</div>
<div className="flex items-center justify-between py-1 px-2">
<div className="flex items-center space-x-2">
<Skeleton className="h-4 w-4 rounded-sm" />
<Skeleton className="h-4 w-52" />
</div>
<Skeleton className="h-4 w-16" />
</div>
</>
) : recentSessions.length > 0 ? (
recentSessions.map((session, index) => (
<div
key={session.id}

View File

@@ -651,9 +651,39 @@ const createChat = async (
// We need to wait for the window to load before we can access localStorage
mainWindow.webContents.on('did-finish-load', () => {
const configStr = JSON.stringify(windowConfig).replace(/'/g, "\\'");
mainWindow.webContents.executeJavaScript(`
localStorage.setItem('gooseConfig', '${configStr}')
`);
// Add error handling and retry logic for localStorage access
mainWindow.webContents
.executeJavaScript(
`
try {
if (typeof Storage !== 'undefined' && window.localStorage) {
localStorage.setItem('gooseConfig', '${configStr}');
} else {
console.warn('localStorage not available, retrying in 100ms');
setTimeout(() => {
try {
localStorage.setItem('gooseConfig', '${configStr}');
} catch (e) {
console.error('Failed to set localStorage after retry:', e);
}
}, 100);
}
} catch (e) {
console.error('Failed to access localStorage:', e);
// Retry after a short delay
setTimeout(() => {
try {
localStorage.setItem('gooseConfig', '${configStr}');
} catch (retryError) {
console.error('Failed to set localStorage after retry:', retryError);
}
}, 100);
}
`
)
.catch((error) => {
console.error('Failed to execute localStorage script:', error);
});
});
// Handle new window creation for links

View File

@@ -116,7 +116,20 @@ type AppConfigAPI = {
const electronAPI: ElectronAPI = {
platform: process.platform,
reactReady: () => ipcRenderer.send('react-ready'),
getConfig: () => config,
getConfig: () => {
// Add fallback to localStorage if config from preload is empty or missing
if (!config || Object.keys(config).length === 0) {
try {
const storedConfig = localStorage.getItem('gooseConfig');
if (storedConfig) {
return JSON.parse(storedConfig);
}
} catch (e) {
console.warn('Failed to parse stored config from localStorage:', e);
}
}
return config;
},
hideWindow: () => ipcRenderer.send('hide-window'),
directoryChooser: (replace?: boolean) => ipcRenderer.invoke('directory-chooser', replace),
createChatWindow: (