From 9eb6353e00873260a47ac59c8a9b5a21765fe0a1 Mon Sep 17 00:00:00 2001 From: Khalid Abdi Date: Sun, 14 Jun 2026 19:43:55 +0300 Subject: [PATCH] frontend: dismissible chat error alert + compact import card - the inline chat error now has an X to dismiss it, and is the single surface for errors (dropped the duplicate toast) so failures show once and clearly - the import preview card caps the skipped-rows list (scrolls, one line each with a "+N more" tail) so a file full of validation errors no longer renders a giant card Co-Authored-By: Claude Opus 4.8 --- frontend/components/chat/chat-panel.tsx | 49 +++++++++++-------- .../components/chat/import-preview-card.tsx | 12 +++-- frontend/lib/i18n/locales/en/translation.json | 4 +- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/frontend/components/chat/chat-panel.tsx b/frontend/components/chat/chat-panel.tsx index 463b1fd..46ff655 100644 --- a/frontend/components/chat/chat-panel.tsx +++ b/frontend/components/chat/chat-panel.tsx @@ -2,7 +2,7 @@ import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; -import { AlertTriangle, ShieldCheck } from "lucide-react"; +import { AlertTriangle, ShieldCheck, X } from "lucide-react"; import { nanoid } from "nanoid"; import { useSearchParams } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ -47,7 +47,6 @@ import type { TemetroUIMessage } from "@/lib/ai-chat"; import { getAiConfig } from "@/lib/ai-settings"; import { API_BASE_URL } from "@/lib/api-client"; import { getPatient } from "@/lib/patients"; -import { notify } from "@/lib/toast"; // Trigger: `/patient 10293` or just `/10293` — a client-side fast-path that // pulls records instantly without the LLM (also works offline). @@ -96,12 +95,13 @@ export function ChatPanel() { }; }, []); - // Pop a toast whenever a request errors, so failures are never silent. + // Surface errors inline (and dismissible) instead of as a toast, so a failure + // stays visible until acknowledged and isn't duplicated. Reset the dismissed + // flag whenever a fresh error arrives. + const [errorDismissed, setErrorDismissed] = useState(false); useEffect(() => { - if (error) { - notify.error(t("chat.error.title"), error.message || t("chat.error.body")); - } - }, [error, t]); + if (error) setErrorDismissed(false); + }, [error]); const isCloudModel = (getModel(model)?.provider ?? "ollama") !== "ollama"; @@ -210,20 +210,29 @@ export function ChatPanel() { /> ) : null; - const errorAlert = error ? ( -
- -
-

{t("chat.error.title")}

-

- {error.message || t("chat.error.body")} -

+ const errorAlert = + error && !errorDismissed ? ( +
+ +
+

{t("chat.error.title")}

+

+ {error.message || t("chat.error.body")} +

+
+
-
- ) : null; + ) : null; // Render one assistant/user message: a Chain-of-Thought trace built from any // `data-step` parts, then the rest of the parts (text + record cards) in order. diff --git a/frontend/components/chat/import-preview-card.tsx b/frontend/components/chat/import-preview-card.tsx index 63512d0..a924344 100644 --- a/frontend/components/chat/import-preview-card.tsx +++ b/frontend/components/chat/import-preview-card.tsx @@ -67,13 +67,19 @@ export function ImportPreviewCard({ data }: { data: ImportPreviewData }) {
{data.invalid.length > 0 ? ( -