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:
@@ -10,7 +10,7 @@
|
|||||||
"license": {
|
"license": {
|
||||||
"name": "Apache-2.0"
|
"name": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"version": "1.0.36"
|
"version": "1.1.0"
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"/agent/tools": {
|
"/agent/tools": {
|
||||||
|
|||||||
@@ -446,9 +446,6 @@ function BaseChatContent({
|
|||||||
|
|
||||||
{/* Custom content after messages */}
|
{/* Custom content after messages */}
|
||||||
{renderAfterMessages && renderAfterMessages()}
|
{renderAfterMessages && renderAfterMessages()}
|
||||||
|
|
||||||
{/* Bottom padding to make space for the loading indicator */}
|
|
||||||
<div className="block h-20" />
|
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
||||||
{/* Fixed loading indicator at bottom left of chat container */}
|
{/* Fixed loading indicator at bottom left of chat container */}
|
||||||
|
|||||||
@@ -427,7 +427,6 @@ export default function ChatInput({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [numTokens, toolCount, tokenLimit, isTokenLimitLoaded, addAlert, clearAlerts]);
|
}, [numTokens, toolCount, tokenLimit, isTokenLimitLoaded, addAlert, clearAlerts]);
|
||||||
|
|
||||||
const minHeight = '1rem';
|
|
||||||
const maxHeight = 10 * 24;
|
const maxHeight = 10 * 24;
|
||||||
|
|
||||||
// Debounced function to update actual value
|
// Debounced function to update actual value
|
||||||
@@ -456,6 +455,13 @@ export default function ChatInput({
|
|||||||
}
|
}
|
||||||
}, [debouncedAutosize, displayValue]);
|
}, [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 handleChange = (evt: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
const val = evt.target.value;
|
const val = evt.target.value;
|
||||||
const cursorPosition = evt.target.selectionStart;
|
const cursorPosition = evt.target.selectionStart;
|
||||||
@@ -910,7 +916,6 @@ export default function ChatInput({
|
|||||||
ref={textAreaRef}
|
ref={textAreaRef}
|
||||||
rows={1}
|
rows={1}
|
||||||
style={{
|
style={{
|
||||||
minHeight: `${minHeight}px`,
|
|
||||||
maxHeight: `${maxHeight}px`,
|
maxHeight: `${maxHeight}px`,
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
opacity: isRecording ? 0 : 1,
|
opacity: isRecording ? 0 : 1,
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ export default function Pair({
|
|||||||
onMessageStreamFinish={handleMessageStreamFinish}
|
onMessageStreamFinish={handleMessageStreamFinish}
|
||||||
renderBeforeMessages={renderBeforeMessages}
|
renderBeforeMessages={renderBeforeMessages}
|
||||||
customChatInputProps={customChatInputProps}
|
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
|
showPopularTopics={!isTransitioningFromHub} // Don't show popular topics while transitioning from Hub
|
||||||
suppressEmptyState={isTransitioningFromHub} // Suppress all empty state content while transitioning from Hub
|
suppressEmptyState={isTransitioningFromHub} // Suppress all empty state content while transitioning from Hub
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export function SessionInsights() {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [recentSessions, setRecentSessions] = useState<Session[]>([]);
|
const [recentSessions, setRecentSessions] = useState<Session[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [isLoadingSessions, setIsLoadingSessions] = useState(true);
|
||||||
// const [recentProjects, setRecentProjects] = useState<ProjectMetadata[]>([]);
|
// const [recentProjects, setRecentProjects] = useState<ProjectMetadata[]>([]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -67,7 +68,8 @@ export function SessionInsights() {
|
|||||||
setRecentSessions(sessions.slice(0, 3));
|
setRecentSessions(sessions.slice(0, 3));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load recent sessions:', 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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1 min-h-[96px] transition-all duration-300 ease-in-out">
|
<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) => (
|
recentSessions.map((session, index) => (
|
||||||
<div
|
<div
|
||||||
key={session.id}
|
key={session.id}
|
||||||
|
|||||||
+33
-3
@@ -651,9 +651,39 @@ const createChat = async (
|
|||||||
// We need to wait for the window to load before we can access localStorage
|
// We need to wait for the window to load before we can access localStorage
|
||||||
mainWindow.webContents.on('did-finish-load', () => {
|
mainWindow.webContents.on('did-finish-load', () => {
|
||||||
const configStr = JSON.stringify(windowConfig).replace(/'/g, "\\'");
|
const configStr = JSON.stringify(windowConfig).replace(/'/g, "\\'");
|
||||||
mainWindow.webContents.executeJavaScript(`
|
// Add error handling and retry logic for localStorage access
|
||||||
localStorage.setItem('gooseConfig', '${configStr}')
|
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
|
// Handle new window creation for links
|
||||||
|
|||||||
@@ -116,7 +116,20 @@ type AppConfigAPI = {
|
|||||||
const electronAPI: ElectronAPI = {
|
const electronAPI: ElectronAPI = {
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
reactReady: () => ipcRenderer.send('react-ready'),
|
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'),
|
hideWindow: () => ipcRenderer.send('hide-window'),
|
||||||
directoryChooser: (replace?: boolean) => ipcRenderer.invoke('directory-chooser', replace),
|
directoryChooser: (replace?: boolean) => ipcRenderer.invoke('directory-chooser', replace),
|
||||||
createChatWindow: (
|
createChatWindow: (
|
||||||
|
|||||||
Reference in New Issue
Block a user