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 : [],
+ }
+}