diff --git a/apps/nextjs/src/app/api/assistant/chat/route.ts b/apps/nextjs/src/app/api/assistant/chat/route.ts
index c49447a41..b41f4cf85 100644
--- a/apps/nextjs/src/app/api/assistant/chat/route.ts
+++ b/apps/nextjs/src/app/api/assistant/chat/route.ts
@@ -27,7 +27,8 @@ const getToolApprovalSecret = () =>
Buffer.from(
hkdfSync("sha256", Buffer.from(env.SECRET_ENCRYPTION_KEY, "hex"), "", "assistant-tool-approval", 32),
).toString("base64url");
-const safeStreamError = "The model endpoint stopped the response. Check its URL, model, and credentials.";
+const safeStreamError =
+ "The model endpoint stopped the response. Try again, or ask an administrator to verify its URL, model, and credentials.";
const requestSchema = z.object({
id: z.string().min(1).max(64),
@@ -267,9 +268,15 @@ export async function POST(request: Request) {
});
return result.toUIMessageStreamResponse({ onError: () => safeStreamError });
- } catch {
+ } catch (error) {
+ logger.error("Assistant response could not start", {
+ error: error instanceof Error ? error.message : String(error),
+ });
return Response.json(
- { error: "The configured model endpoint could not start this response. Check its URL, model, and credentials." },
+ {
+ error:
+ "The configured model endpoint could not start this response. Try again, or ask an administrator to verify its URL, model, and credentials.",
+ },
{ status: 502 },
);
}
diff --git a/apps/nextjs/src/components/assistant/assistant-panel.module.css b/apps/nextjs/src/components/assistant/assistant-panel.module.css
index ea88c6365..4cb0d06a7 100644
--- a/apps/nextjs/src/components/assistant/assistant-panel.module.css
+++ b/apps/nextjs/src/components/assistant/assistant-panel.module.css
@@ -85,7 +85,12 @@
border-radius: var(--mantine-radius-md);
color: var(--mantine-color-red-text);
background: var(--mantine-color-red-light);
+}
+
+.messageErrorText {
+ color: var(--mantine-color-text);
font-size: var(--mantine-font-size-sm);
+ line-height: 1.45;
}
.tool {
diff --git a/apps/nextjs/src/components/assistant/assistant-panel.tsx b/apps/nextjs/src/components/assistant/assistant-panel.tsx
index c5a2c4640..046b67af4 100644
--- a/apps/nextjs/src/components/assistant/assistant-panel.tsx
+++ b/apps/nextjs/src/components/assistant/assistant-panel.tsx
@@ -5,6 +5,7 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import {
ActionBarPrimitive,
ComposerPrimitive,
+ ErrorPrimitive,
MessagePartPrimitive,
MessagePrimitive,
ThreadListItemPrimitive,
@@ -16,6 +17,7 @@ import { ActionIcon, Box, Button, Drawer, Group, ScrollArea, Stack, Text, ThemeI
import { useMediaQuery } from "@mantine/hooks";
import {
IconActivityHeartbeat,
+ IconAlertTriangle,
IconApps,
IconArchive,
IconArrowUp,
@@ -151,7 +153,30 @@ const RuntimeError = () => {
const t = useScopedI18n("common.assistant");
return (
- {t("responseError")}
+
+
+
+
+
+
+
+ {t("responseError.title")}
+
+
+
+ }
+ >
+ {t("responseError.retry")}
+
+
+
+
+
);
};
diff --git a/apps/nextjs/src/components/assistant/assistant-provider.tsx b/apps/nextjs/src/components/assistant/assistant-provider.tsx
index 012b40610..a342c042e 100644
--- a/apps/nextjs/src/components/assistant/assistant-provider.tsx
+++ b/apps/nextjs/src/components/assistant/assistant-provider.tsx
@@ -21,6 +21,7 @@ import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
import { clientApi, fetchApi } from "@homarr/api/client";
import { useSession } from "@homarr/auth/client";
import { hotkeys } from "@homarr/definitions";
+import { showErrorNotification } from "@homarr/notifications";
import { useScopedI18n } from "@homarr/translation/client";
import {
openMediaRequestSearch,
@@ -177,12 +178,23 @@ const createHistoryAdapter = (threadId: string | undefined): ThreadHistoryAdapte
});
const AssistantThreadRuntime = () => {
+ const t = useScopedI18n("common.assistant");
const threadId = useAuiState((state) => state.threadListItem.remoteId);
const transport = useMemo(() => new AssistantChatTransport({ api: "/api/assistant/chat" }), []);
const history = useMemo(() => createHistoryAdapter(threadId), [threadId]);
+ const onError = useCallback(
+ () =>
+ showErrorNotification({
+ title: t("responseError.title"),
+ message: t("responseError.description"),
+ autoClose: 10_000,
+ }),
+ [t],
+ );
return useChatRuntime({
transport,
+ onError,
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
adapters: { history },
});
diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json
index 2248155e7..79dbb8f0d 100644
--- a/packages/translation/src/lang/en.json
+++ b/packages/translation/src/lang/en.json
@@ -2024,7 +2024,11 @@
"send": "Send message",
"copy": "Copy response",
"regenerate": "Regenerate response",
- "responseError": "The response stopped unexpectedly. Try again, or ask an administrator to verify the model endpoint.",
+ "responseError": {
+ "title": "The assistant could not finish",
+ "description": "Try again. If the problem continues, ask an administrator to verify the configured model endpoint.",
+ "retry": "Try again"
+ },
"scrollToLatest": "Scroll to latest",
"approvalRequired": "Approval required",
"complete": "Complete",