From 38282a37d68e6dc9827f5734d5b8603067b7f480 Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Sat, 28 Mar 2026 05:52:41 +0000 Subject: [PATCH] feat: warning when model is unavailable in the region (#814) --- apps/web/components/chat/index.tsx | 80 ++++++++++++++++++++++++++++-- apps/web/instrumentation-client.ts | 23 +++++++++ apps/web/lib/chat-stream-error.ts | 41 +++++++++++++++ 3 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 apps/web/lib/chat-stream-error.ts diff --git a/apps/web/components/chat/index.tsx b/apps/web/components/chat/index.tsx index f7ea699f..106d2f7d 100644 --- a/apps/web/components/chat/index.tsx +++ b/apps/web/components/chat/index.tsx @@ -21,7 +21,6 @@ import { Check, ChevronDownIcon, HistoryIcon, - PanelRightCloseIcon, Plus, SearchIcon, SquarePenIcon, @@ -33,11 +32,12 @@ import { cn } from "@lib/utils" import { dmSansClassName } from "@/lib/fonts" import ChatInput from "./input" import ChatModelSelector from "./model-selector" +import { getNovaChatErrorCopy } from "@/lib/chat-stream-error" import { GradientLogo, LogoBgGradient } from "@ui/assets/Logo" import { useProject } from "@/stores" import { useContainerTags } from "@/hooks/use-container-tags" import { getChatSpaceDisplayLabel } from "@/lib/chat-space-label" -import type { ModelId } from "@/lib/models" +import { modelNames, type ModelId } from "@/lib/models" import { SuperLoader } from "../superloader" import { UserMessage } from "./message/user-message" import { AgentMessage } from "./message/agent-message" @@ -197,11 +197,32 @@ export function ChatSidebar({ return () => window.removeEventListener("scroll", handleWindowScroll) }, [isMobile, viewMode]) - const { messages, sendMessage, status, setMessages, stop } = useChat({ + const { + messages, + sendMessage, + status, + setMessages, + stop, + error, + clearError, + } = useChat({ id: currentChatId ?? undefined, transport: chatTransport, }) + const chatStreamError = useMemo( + () => (error ? getNovaChatErrorCopy(error, selectedModel) : null), + [error, selectedModel], + ) + + const handleModelChange = useCallback( + (modelId: ModelId) => { + setSelectedModel(modelId) + clearError() + }, + [clearError], + ) + useEffect(() => { if (pendingThreadLoad && currentChatId === pendingThreadLoad.id) { setMessages(pendingThreadLoad.messages) @@ -550,7 +571,7 @@ export function ChatSidebar({
)} + {chatStreamError && ( +
+
+
+

+ {chatStreamError.title} +

+

+ {chatStreamError.body} +

+ {chatStreamError.otherModels.length > 0 && ( +
+ {chatStreamError.otherModels.map((id) => { + const m = modelNames[id] + return ( + + ) + })} +
+ )} +
+ +
+
+ )} + setInput(e.target.value)} diff --git a/apps/web/instrumentation-client.ts b/apps/web/instrumentation-client.ts index f6730e9c..c01e84a2 100644 --- a/apps/web/instrumentation-client.ts +++ b/apps/web/instrumentation-client.ts @@ -4,6 +4,24 @@ import * as Sentry from "@sentry/nextjs" +function sentryShouldDropExpectedNonActionableError(event: { + message?: string + exception?: { values?: Array<{ type?: string; value?: string }> } +}): boolean { + const patterns = [ + /user location is not supported/i, + /this email domain is not allowed/i, + ] + const matches = (s: string | undefined) => + s != null && patterns.some((re) => re.test(s)) + + if (matches(event.message)) return true + for (const ex of event.exception?.values ?? []) { + if (matches(ex.value)) return true + } + return false +} + Sentry.init({ dsn: "https://2451ebfd1a7490f05fa7776482df81b6@o4508385422802944.ingest.us.sentry.io/4509872269819904", @@ -25,6 +43,11 @@ Sentry.init({ // Setting this option to true will print useful information to the console while you're setting up Sentry. debug: false, + + beforeSend(event) { + if (sentryShouldDropExpectedNonActionableError(event)) return null + return event + }, }) export const onRouterTransitionStart = Sentry.captureRouterTransitionStart diff --git a/apps/web/lib/chat-stream-error.ts b/apps/web/lib/chat-stream-error.ts new file mode 100644 index 00000000..ab236225 --- /dev/null +++ b/apps/web/lib/chat-stream-error.ts @@ -0,0 +1,41 @@ +import type { ModelId } from "@/lib/models" + +const OTHER_MODELS: ModelId[] = ["gpt-5", "claude-sonnet-4.5"] + +function flattenError(e: unknown): string { + if (e == null) return "" + if (typeof e === "string") return e + if (e instanceof Error) { + const parts = [e.message] + for (let c: unknown = e.cause; c instanceof Error; c = c.cause) { + parts.push(c.message) + } + return parts.join(" ") + } + return String(e) +} + +export function getNovaChatErrorCopy(error: unknown, model: ModelId) { + const msg = flattenError(error) + const geminiGeo = + /user location is not supported/i.test(msg) || + (/failed_precondition/i.test(msg) && /location is not supported/i.test(msg)) + + if (geminiGeo) { + return { + title: "This model isn't available in your region", + body: "Gemini can't be used from your location. Try another model above.", + otherModels: OTHER_MODELS.filter((id) => id !== model), + } + } + + const body = + msg.length > 200 + ? `${msg.slice(0, 197).trim()}…` + : msg || "Try again or switch models." + return { + title: "Something went wrong", + body, + otherModels: model === "gemini-2.5-pro" ? OTHER_MODELS : [], + } +}