diff --git a/public/app/features/dashboard/components/GenAI/GenAIButton.test.tsx b/public/app/features/dashboard/components/GenAI/GenAIButton.test.tsx
index 44dc08ec314..dc94a9ae1e0 100644
--- a/public/app/features/dashboard/components/GenAI/GenAIButton.test.tsx
+++ b/public/app/features/dashboard/components/GenAI/GenAIButton.test.tsx
@@ -208,7 +208,9 @@ describe('GenAIButton', () => {
// The tooltip keeps interactive to be able to click the link
await userEvent.hover(tooltip);
expect(tooltip).toBeVisible();
- expect(tooltip).toHaveTextContent('Something went wrong');
+ expect(tooltip).toHaveTextContent(
+ 'Failed to generate content using OpenAI. Please try again or if the problem persist, contact your organization admin.'
+ );
});
it('should call the onClick callback', async () => {
diff --git a/public/app/features/dashboard/components/GenAI/GenAIButton.tsx b/public/app/features/dashboard/components/GenAI/GenAIButton.tsx
index 2a69bc7c0f9..12a76bf4bd5 100644
--- a/public/app/features/dashboard/components/GenAI/GenAIButton.tsx
+++ b/public/app/features/dashboard/components/GenAI/GenAIButton.tsx
@@ -173,7 +173,13 @@ export const GenAIButton = ({
{isFirstHistoryEntry && }
{!hasHistory && (
-
+
{button}
)}
diff --git a/public/app/features/dashboard/components/GenAI/hooks.ts b/public/app/features/dashboard/components/GenAI/hooks.ts
index 02cc60b89e8..e3857500aef 100644
--- a/public/app/features/dashboard/components/GenAI/hooks.ts
+++ b/public/app/features/dashboard/components/GenAI/hooks.ts
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import { useAsync } from 'react-use';
import { Subscription } from 'rxjs';
@@ -18,6 +18,8 @@ export enum StreamStatus {
COMPLETED = 'completed',
}
+export const TIMEOUT = 10000;
+
// TODO: Add tests
export function useOpenAIStream(
model = OPEN_AI_MODEL,
@@ -46,6 +48,21 @@ export function useOpenAIStream(
const [error, setError] = useState();
const { error: notifyError } = useAppNotification();
+ const onError = useCallback(
+ (e: Error) => {
+ setStreamStatus(StreamStatus.IDLE);
+ setMessages([]);
+ setError(e);
+ notifyError(
+ 'Failed to generate content using OpenAI',
+ `Please try again or if the problem persists, contact your organization admin.`
+ );
+ console.error(e);
+ logError(e, { messages: JSON.stringify(messages), model, temperature: String(temperature) });
+ },
+ [messages, model, temperature, notifyError]
+ );
+
const { error: enabledError, value: enabled } = useAsync(
async () => await isLLMPluginEnabled(),
[isLLMPluginEnabled]
@@ -80,13 +97,7 @@ export function useOpenAIStream(
enabled,
stream: stream.subscribe({
next: setReply,
- error: (e: Error) => {
- setStreamStatus(StreamStatus.IDLE);
- setMessages([]);
- setError(e);
- notifyError('OpenAI Error', `${e.message}`);
- logError(e, { messages: JSON.stringify(messages), model, temperature: String(temperature) });
- },
+ error: onError,
complete: () => {
setStreamStatus(StreamStatus.COMPLETED);
setTimeout(() => {
@@ -99,6 +110,28 @@ export function useOpenAIStream(
};
}, [messages, enabled]);
+ // Unsubscribe from the stream when the component unmounts.
+ useEffect(() => {
+ return () => {
+ if (value?.stream) {
+ value.stream.unsubscribe();
+ }
+ };
+ }, [value]);
+
+ // If the stream is generating and we haven't received a reply, it times out.
+ useEffect(() => {
+ let timeout: NodeJS.Timeout | undefined;
+ if (streamStatus === StreamStatus.GENERATING && reply === '') {
+ timeout = setTimeout(() => {
+ onError(new Error(`OpenAI stream timed out after ${TIMEOUT}ms`));
+ }, TIMEOUT);
+ }
+ return () => {
+ timeout && clearTimeout(timeout);
+ };
+ }, [streamStatus, reply, onError]);
+
if (asyncError || enabledError) {
setError(asyncError || enabledError);
}