Skip to content

Commit 9bd1ca5

Browse files
committed
fix: improve error handling and user feedback in authentication and chat components
- Added error logging in LoginForm for OAuth result handling. - Introduced auto-focus functionality for the textarea in ChatForm based on flow status. - Enhanced ChatMessages to conditionally display a message when no active tasks are present. - Updated OAuthResult to provide user feedback during authentication process with status messages and improved window handling.
1 parent b14fd6b commit 9bd1ca5

File tree

6 files changed

+135
-32
lines changed

6 files changed

+135
-32
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
"DATABASE_URL": "postgres://postgres:postgres@localhost:5432/pentagidb?sslmode=disable",
101101
},
102102
"args": [
103-
"-cmd", "reindex",
103+
"info", "-verbose",
104104
],
105105
"cwd": "${workspaceFolder}",
106106
"output": "${workspaceFolder}/build/__debug_bin_etester",

frontend/src/features/authentication/LoginForm.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ const LoginForm = ({ providers, returnUrl = '/chat/new' }: LoginFormProps) => {
162162
}
163163
} catch {
164164
// In case of error, fall through to common handling below
165+
console.error('error during OAuth result handling:', error);
165166
}
166167
}
167168

frontend/src/features/chat/ChatForm.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ interface ChatFormProps {
2222

2323
const ChatForm = ({ selectedFlowId, flowStatus, onSubmit }: ChatFormProps) => {
2424
const [isSubmitting, setIsSubmitting] = useState(false);
25+
const textareaId = 'chat-textarea';
2526

2627
const form = useForm<z.infer<typeof formSchema>>({
2728
resolver: zodResolver(formSchema),
@@ -44,6 +45,10 @@ const ChatForm = ({ selectedFlowId, flowStatus, onSubmit }: ChatFormProps) => {
4445
return 'What would you like me to help you with?';
4546
}
4647

48+
if (flowStatus === StatusType.Waiting) {
49+
return 'Continue the conversation...';
50+
}
51+
4752
if (flowStatus === StatusType.Finished) {
4853
return 'The flow is finished';
4954
}
@@ -86,6 +91,20 @@ const ChatForm = ({ selectedFlowId, flowStatus, onSubmit }: ChatFormProps) => {
8691
isSubmitting ||
8792
(selectedFlowId !== 'new' && (!flowStatus || ![StatusType.Waiting].includes(flowStatus)));
8893

94+
// Auto-focus on textarea when needed
95+
useEffect(() => {
96+
if (
97+
!isDisabled &&
98+
(selectedFlowId === 'new' || flowStatus === StatusType.Waiting)
99+
) {
100+
const textarea = document.querySelector(`#${textareaId}`) as HTMLTextAreaElement;
101+
if (textarea) {
102+
const timeoutId = setTimeout(() => textarea.focus(), 0);
103+
return () => clearTimeout(timeoutId);
104+
}
105+
}
106+
}, [selectedFlowId, flowStatus, isDisabled]);
107+
89108
return (
90109
<Form {...form}>
91110
<form
@@ -99,6 +118,7 @@ const ChatForm = ({ selectedFlowId, flowStatus, onSubmit }: ChatFormProps) => {
99118
<FormControl>
100119
<Textarea
101120
{...field}
121+
id={textareaId}
102122
placeholder={getPlaceholderText()}
103123
disabled={isDisabled}
104124
onKeyDown={handleKeyDown}

frontend/src/features/chat/ChatMessages.tsx

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { cn } from '@/lib/utils';
1313
import ChatMessage from './ChatMessage';
1414

1515
interface ChatMessagesProps {
16+
selectedFlowId: string | null;
1617
logs?: MessageLogFragmentFragment[];
1718
className?: string;
1819
}
@@ -21,7 +22,7 @@ const searchFormSchema = z.object({
2122
search: z.string(),
2223
});
2324

24-
const ChatMessages = ({ logs, className }: ChatMessagesProps) => {
25+
const ChatMessages = ({ selectedFlowId, logs, className }: ChatMessagesProps) => {
2526
const messagesEndRef = useRef<HTMLDivElement>(null);
2627

2728
// Memoize the scroll function to avoid recreating it on every render
@@ -64,7 +65,7 @@ const ChatMessages = ({ logs, className }: ChatMessagesProps) => {
6465
}, [filteredLogs.length, scrollMessages]);
6566

6667
return (
67-
<div className={cn('flex flex-col', className)}>
68+
<div className={cn('flex h-full flex-col', className)}>
6869
<div className="sticky top-0 z-10 bg-background py-4">
6970
<Form {...form}>
7071
<FormField
@@ -97,15 +98,25 @@ const ChatMessages = ({ logs, className }: ChatMessagesProps) => {
9798
/>
9899
</Form>
99100
</div>
100-
<div className="space-y-4 pb-4">
101-
{filteredLogs.map((log) => (
102-
<ChatMessage
103-
key={log.id}
104-
log={log}
105-
/>
106-
))}
107-
<div ref={messagesEndRef} />
108-
</div>
101+
102+
{filteredLogs.length > 0 || selectedFlowId !== 'new' ? (
103+
<div className="flex-1 space-y-4 overflow-y-auto pb-4">
104+
{filteredLogs.map((log) => (
105+
<ChatMessage
106+
key={log.id}
107+
log={log}
108+
/>
109+
))}
110+
<div ref={messagesEndRef} />
111+
</div>
112+
) : (
113+
<div className="flex flex-1 items-center justify-center">
114+
<div className="flex flex-col items-center gap-2 text-muted-foreground">
115+
<p>No Active Tasks</p>
116+
<p className="text-xs">Starting a new task may take some time as the PentAGI agent downloads the required Docker image</p>
117+
</div>
118+
</div>
119+
)}
109120
</div>
110121
);
111122
};

frontend/src/pages/Chat.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,10 @@ const Chat = () => {
381381
>
382382
<Card className="flex h-[calc(100dvh-3rem)] flex-col rounded-none border-0">
383383
<CardContent className="flex-1 overflow-y-auto pb-0">
384-
<ChatMessages logs={flowData?.messageLogs ?? []} />
384+
<ChatMessages
385+
selectedFlowId={selectedFlowId}
386+
logs={flowData?.messageLogs ?? []}
387+
/>
385388
</CardContent>
386389
<CardFooter className="sticky bottom-0 border-t bg-background pt-4">
387390
<ChatForm

frontend/src/pages/OAuthResult.tsx

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,111 @@
1-
import { useEffect } from 'react';
1+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
22

33
import Logo from '@/components/icons/Logo';
44
import ThemeProvider from '@/providers/ThemeProvider';
55

66
const OAuthResult = () => {
7+
const [statusMessage, setStatusMessage] = useState('Authentication in progress...');
8+
const messageRef = useRef(statusMessage);
9+
const prevMessageRef = useRef(statusMessage);
10+
11+
// Success delay is short, error delay is longer to allow reading
12+
const successDelay = 2000;
13+
const errorDelay = 5000;
14+
15+
// Synchronize state with ref without problematic dependencies
16+
useLayoutEffect(() => {
17+
if (prevMessageRef.current !== messageRef.current) {
18+
setStatusMessage(messageRef.current);
19+
prevMessageRef.current = messageRef.current;
20+
}
21+
}, []);
22+
723
useEffect(() => {
824
const params = new URLSearchParams(window.location.search);
925
const status = params.get('status');
1026
const error = params.get('error');
1127

12-
if (window.opener) {
13-
window.opener.postMessage(
14-
{
15-
type: 'oauth-result',
16-
status,
17-
error,
18-
},
19-
window.location.origin,
20-
);
21-
22-
const closeWindow = new Promise<void>((resolve) => {
23-
const timer = setTimeout(() => {
28+
// This is used to track all timeouts that need to be cleared on cleanup
29+
let redirectTimer: NodeJS.Timeout | null = null;
30+
let cleanupTimer: NodeJS.Timeout | null = null;
31+
let closeTimer: NodeJS.Timeout | null = null;
32+
33+
const updateMessage = (message: string) => {
34+
messageRef.current = message;
35+
};
36+
37+
// Handle window close safely
38+
const handleClose = (delay: number) => {
39+
closeTimer = setTimeout(() => {
40+
try {
2441
if (window && !window.closed) {
2542
window.close();
2643
}
27-
resolve();
28-
}, 10000);
44+
} catch (e) {
45+
console.error('Delayed window close failed:', e);
46+
}
47+
}, delay);
48+
};
49+
50+
// Handle redirection if needed
51+
const handleRedirect = (url: string, delay: number) => {
52+
redirectTimer = setTimeout(() => {
53+
try {
54+
window.location.href = url;
55+
} catch (e) {
56+
console.error('Redirection failed:', e);
57+
}
58+
}, delay);
59+
60+
// Ensure redirect timer gets cleaned up
61+
cleanupTimer = setTimeout(() => {
62+
if (redirectTimer) {
63+
clearTimeout(redirectTimer);
64+
redirectTimer = null;
65+
}
66+
}, delay + 100);
67+
};
2968

30-
return () => clearTimeout(timer);
31-
});
69+
if (window.opener) {
70+
try {
71+
window.opener.postMessage(
72+
{
73+
type: 'oauth-result',
74+
status,
75+
error,
76+
},
77+
window.location.origin,
78+
);
3279

33-
closeWindow.catch(() => window.close());
80+
// Success handling
81+
updateMessage('Authentication complete, closing window...');
82+
handleClose(successDelay);
83+
} catch (e) {
84+
console.error('Failed to send message to opener:', e);
85+
updateMessage('Error communicating with parent window. Closing in a few seconds...');
86+
handleClose(errorDelay);
87+
}
88+
} else {
89+
// If no opener, redirect to login
90+
updateMessage('Authentication window opened directly. Redirecting to login page...');
91+
handleRedirect('/login', errorDelay / 2);
92+
handleClose(errorDelay);
3493
}
35-
}, []);
94+
95+
// Cleanup function for useEffect
96+
return () => {
97+
// Explicitly clear all timeouts
98+
if (redirectTimer) clearTimeout(redirectTimer);
99+
if (cleanupTimer) clearTimeout(cleanupTimer);
100+
if (closeTimer) clearTimeout(closeTimer);
101+
};
102+
}, [successDelay, errorDelay]);
36103

37104
return (
38105
<ThemeProvider>
39106
<div className="flex h-screen w-full items-center justify-center bg-gradient-to-r from-slate-800 to-slate-950">
40107
<Logo className="m-auto size-32 animate-logo-spin text-white delay-10000" />
108+
<div className="fixed bottom-4 text-sm text-white">{statusMessage}</div>
41109
</div>
42110
</ThemeProvider>
43111
);

0 commit comments

Comments
 (0)