fix(ai-chat): improve error banner layout
The AI chat error Alert stranded the warning icon in the top-left while
the detail text hung indented under the heading, wrapping to 3 narrow
lines with empty space below. Switch to a "full-width detail" layout
(icon + bold heading on the first row, detail spanning full width below)
and extract the markup, previously duplicated in ChatThread and
MessageItem, into a single shared ChatErrorAlert component.
- add apps/client/src/features/ai-chat/components/chat-error-alert.tsx
- use it for the live stream error in chat-thread.tsx (mb="xs")
- use it for the persisted history error in message-item.tsx (mt={4})
- heading/icon use the adaptive --mantine-color-red-light-color so the
banner stays correct in dark mode
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { Alert, Group, Text, type AlertProps } from "@mantine/core";
|
||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||
|
||||
/**
|
||||
* A classified AI chat error banner: a warning icon + bold heading on the first
|
||||
* row, with the detail text spanning the full width below. Rendered for BOTH the
|
||||
* live stream error (ChatThread) and a persisted assistant error (MessageItem),
|
||||
* so this markup lives in one place. The detail is full-width (no hanging indent
|
||||
* under the heading) so it wraps less and leaves no stranded icon / empty gap.
|
||||
* The heading reuses Mantine's adaptive red "light" colour so it stays correct
|
||||
* in dark mode. Layout-only props (mb/mt/...) are forwarded to the Alert root.
|
||||
*/
|
||||
interface ChatErrorAlertProps extends Omit<AlertProps, "title" | "children"> {
|
||||
title: string;
|
||||
detail: string;
|
||||
}
|
||||
|
||||
export default function ChatErrorAlert({
|
||||
title,
|
||||
detail,
|
||||
...alertProps
|
||||
}: ChatErrorAlertProps) {
|
||||
// Mantine's own "light" alert colour, adaptive across light/dark schemes.
|
||||
const accent = "var(--mantine-color-red-light-color)";
|
||||
return (
|
||||
<Alert {...alertProps} variant="light" color="red" p="xs">
|
||||
<Group gap={8} wrap="nowrap" align="center" mb={4}>
|
||||
<IconAlertTriangle size={18} style={{ flex: "none", color: accent }} />
|
||||
<Text fw={700} size="sm" lh={1.2} style={{ color: accent }}>
|
||||
{title}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="sm" lh={1.4}>
|
||||
{detail}
|
||||
</Text>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { generateId } from "ai";
|
||||
import { ActionIcon, Alert, Box, Group, Stack, Text } from "@mantine/core";
|
||||
import { IconAlertTriangle, IconClockHour4, IconX } from "@tabler/icons-react";
|
||||
import { ActionIcon, Box, Group, Stack, Text } from "@mantine/core";
|
||||
import { IconClockHour4, IconX } from "@tabler/icons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useChat, type UIMessage } from "@ai-sdk/react";
|
||||
import { DefaultChatTransport } from "ai";
|
||||
import MessageList from "@/features/ai-chat/components/message-list.tsx";
|
||||
import ChatInput from "@/features/ai-chat/components/chat-input.tsx";
|
||||
import RoleCards from "@/features/ai-chat/components/role-cards.tsx";
|
||||
import ChatErrorAlert from "@/features/ai-chat/components/chat-error-alert.tsx";
|
||||
import {
|
||||
IAiChatMessageRow,
|
||||
IAiRole,
|
||||
@@ -277,15 +278,11 @@ export default function ChatThread({
|
||||
/>
|
||||
|
||||
{errorView && (
|
||||
<Alert
|
||||
variant="light"
|
||||
color="red"
|
||||
icon={<IconAlertTriangle size={16} />}
|
||||
mb="xs"
|
||||
<ChatErrorAlert
|
||||
title={errorView.title}
|
||||
>
|
||||
{errorView.detail}
|
||||
</Alert>
|
||||
detail={errorView.detail}
|
||||
mb="xs"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Stack gap={0} className={classes.inputWrapper}>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Alert, Box, Text } from "@mantine/core";
|
||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||
import { Box, Text } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { UIMessage } from "@ai-sdk/react";
|
||||
import ToolCallCard from "@/features/ai-chat/components/tool-call-card.tsx";
|
||||
import ChatErrorAlert from "@/features/ai-chat/components/chat-error-alert.tsx";
|
||||
import { ToolUiPart, isToolPart } from "@/features/ai-chat/utils/tool-parts.tsx";
|
||||
import { renderChatMarkdown } from "@/features/ai-chat/utils/markdown.ts";
|
||||
import { resolveAssistantName } from "@/features/ai-chat/utils/assistant-name.ts";
|
||||
@@ -118,15 +118,11 @@ export default function MessageItem({
|
||||
// cause plus a one-line detail.
|
||||
const errorView = describeChatError(errorText, t);
|
||||
return (
|
||||
<Alert
|
||||
variant="light"
|
||||
color="red"
|
||||
icon={<IconAlertTriangle size={16} />}
|
||||
mt={4}
|
||||
<ChatErrorAlert
|
||||
title={errorView.title}
|
||||
>
|
||||
{errorView.detail}
|
||||
</Alert>
|
||||
detail={errorView.detail}
|
||||
mt={4}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user