Improve links/buttons for AI errors (#1524)
drive-by: update links for pro banners <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Revamps ChatErrorBox with styled CTA buttons, UTM-tracked links, broader error matching, and adds default help CTAs; updates Pro banners to point to /pro URLs. > > - **Chat**: > - **Error handling/UI**: Expands rate-limit matching to include `"Provider returned error"` and appends default CTAs on generic errors. > - **CTAs**: Replaces underlined links with styled buttons via `ExternalLink` (supports `primary|secondary` variants and icons), and adds upgrade/docs buttons across error states. > - **Links**: Adds UTM parameters to Pro/Docs links; updates exceeded budget CTA to `"Reload or upgrade your subscription"` with new subscription URL. > - **Pro Banners**: > - Adjusts banner click-through URLs in `AiAccessBanner`, `SmartContextBanner`, and `TurboBanner` to `https://www.dyad.sh/pro?...` (removes `#ai`). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 2111954ac9a98dd66f5b3b9e628270ac19713a2d. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
This commit is contained in:
@@ -85,7 +85,7 @@ export function AiAccessBanner() {
|
|||||||
className="w-full py-2 sm:py-2.5 md:py-3 rounded-lg bg-gradient-to-br from-white via-indigo-50 to-sky-100 dark:from-indigo-700 dark:via-indigo-700 dark:to-indigo-900 flex items-center justify-center relative overflow-hidden ring-1 ring-inset ring-black/5 dark:ring-white/10 shadow-sm cursor-pointer transition-all duration-200 hover:shadow-md hover:-translate-y-[1px]"
|
className="w-full py-2 sm:py-2.5 md:py-3 rounded-lg bg-gradient-to-br from-white via-indigo-50 to-sky-100 dark:from-indigo-700 dark:via-indigo-700 dark:to-indigo-900 flex items-center justify-center relative overflow-hidden ring-1 ring-inset ring-black/5 dark:ring-white/10 shadow-sm cursor-pointer transition-all duration-200 hover:shadow-md hover:-translate-y-[1px]"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
IpcClient.getInstance().openExternalUrl(
|
IpcClient.getInstance().openExternalUrl(
|
||||||
"https://www.dyad.sh/pro#ai?utm_source=dyad-app&utm_medium=app&utm_campaign=in-app-banner-ai-access",
|
"https://www.dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=in-app-banner-ai-access",
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -151,7 +151,7 @@ export function SmartContextBanner() {
|
|||||||
className="w-full py-2 sm:py-2.5 md:py-3 rounded-lg bg-gradient-to-br from-emerald-50 via-emerald-100 to-emerald-200 dark:from-emerald-700 dark:via-emerald-700 dark:to-emerald-900 flex items-center justify-center relative overflow-hidden ring-1 ring-inset ring-emerald-900/10 dark:ring-white/10 shadow-sm cursor-pointer transition-all duration-200 hover:shadow-md hover:-translate-y-[1px]"
|
className="w-full py-2 sm:py-2.5 md:py-3 rounded-lg bg-gradient-to-br from-emerald-50 via-emerald-100 to-emerald-200 dark:from-emerald-700 dark:via-emerald-700 dark:to-emerald-900 flex items-center justify-center relative overflow-hidden ring-1 ring-inset ring-emerald-900/10 dark:ring-white/10 shadow-sm cursor-pointer transition-all duration-200 hover:shadow-md hover:-translate-y-[1px]"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
IpcClient.getInstance().openExternalUrl(
|
IpcClient.getInstance().openExternalUrl(
|
||||||
"https://www.dyad.sh/pro#ai?utm_source=dyad-app&utm_medium=app&utm_campaign=in-app-banner-smart-context",
|
"https://www.dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=in-app-banner-smart-context",
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -192,7 +192,7 @@ export function TurboBanner() {
|
|||||||
className="w-full py-2 sm:py-2.5 md:py-3 rounded-lg bg-gradient-to-br from-rose-50 via-rose-100 to-rose-200 dark:from-rose-800 dark:via-fuchsia-800 dark:to-rose-800 flex items-center justify-center relative overflow-hidden ring-1 ring-inset ring-rose-900/10 dark:ring-white/5 shadow-sm cursor-pointer transition-all duration-200 hover:shadow-md hover:-translate-y-[1px]"
|
className="w-full py-2 sm:py-2.5 md:py-3 rounded-lg bg-gradient-to-br from-rose-50 via-rose-100 to-rose-200 dark:from-rose-800 dark:via-fuchsia-800 dark:to-rose-800 flex items-center justify-center relative overflow-hidden ring-1 ring-inset ring-rose-900/10 dark:ring-white/5 shadow-sm cursor-pointer transition-all duration-200 hover:shadow-md hover:-translate-y-[1px]"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
IpcClient.getInstance().openExternalUrl(
|
IpcClient.getInstance().openExternalUrl(
|
||||||
"https://www.dyad.sh/pro#ai?utm_source=dyad-app&utm_medium=app&utm_campaign=in-app-banner-turbo",
|
"https://www.dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=in-app-banner-turbo",
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { IpcClient } from "@/ipc/ipc_client";
|
import { IpcClient } from "@/ipc/ipc_client";
|
||||||
import { X } from "lucide-react";
|
import {
|
||||||
|
X,
|
||||||
|
ExternalLink as ExternalLinkIcon,
|
||||||
|
CircleArrowUp,
|
||||||
|
} from "lucide-react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
|
|
||||||
@@ -17,7 +21,10 @@ export function ChatErrorBox({
|
|||||||
<ChatErrorContainer onDismiss={onDismiss}>
|
<ChatErrorContainer onDismiss={onDismiss}>
|
||||||
{error}
|
{error}
|
||||||
<span className="ml-1">
|
<span className="ml-1">
|
||||||
<ExternalLink href="https://dyad.sh/pro">
|
<ExternalLink
|
||||||
|
href="https://dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=free-quota-error"
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
Access with Dyad Pro
|
Access with Dyad Pro
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
@@ -30,22 +37,24 @@ export function ChatErrorBox({
|
|||||||
// because it also includes this URL in the error message
|
// because it also includes this URL in the error message
|
||||||
if (
|
if (
|
||||||
error.includes("Resource has been exhausted") ||
|
error.includes("Resource has been exhausted") ||
|
||||||
error.includes("https://ai.google.dev/gemini-api/docs/rate-limits")
|
error.includes("https://ai.google.dev/gemini-api/docs/rate-limits") ||
|
||||||
|
error.includes("Provider returned error")
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<ChatErrorContainer onDismiss={onDismiss}>
|
<ChatErrorContainer onDismiss={onDismiss}>
|
||||||
{error}
|
{error}
|
||||||
<span className="ml-1">
|
<div className="mt-2 space-y-2 space-x-2">
|
||||||
<ExternalLink href="https://dyad.sh/pro">
|
<ExternalLink
|
||||||
|
href="https://dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=rate-limit-error"
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
Upgrade to Dyad Pro
|
Upgrade to Dyad Pro
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</span>{" "}
|
|
||||||
or read the
|
|
||||||
<span className="ml-1">
|
|
||||||
<ExternalLink href="https://dyad.sh/docs/help/ai-rate-limit">
|
<ExternalLink href="https://dyad.sh/docs/help/ai-rate-limit">
|
||||||
Rate limit troubleshooting guide.
|
Troubleshooting guide
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</span>
|
</div>
|
||||||
</ChatErrorContainer>
|
</ChatErrorContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -55,7 +64,10 @@ export function ChatErrorBox({
|
|||||||
<ChatInfoContainer onDismiss={onDismiss}>
|
<ChatInfoContainer onDismiss={onDismiss}>
|
||||||
<span>
|
<span>
|
||||||
Looks like you don't have a valid Dyad Pro key.{" "}
|
Looks like you don't have a valid Dyad Pro key.{" "}
|
||||||
<ExternalLink href="https://dyad.sh/pro">
|
<ExternalLink
|
||||||
|
href="https://dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=invalid-pro-key-error"
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
Upgrade to Dyad Pro
|
Upgrade to Dyad Pro
|
||||||
</ExternalLink>{" "}
|
</ExternalLink>{" "}
|
||||||
today.
|
today.
|
||||||
@@ -68,8 +80,11 @@ export function ChatErrorBox({
|
|||||||
<ChatInfoContainer onDismiss={onDismiss}>
|
<ChatInfoContainer onDismiss={onDismiss}>
|
||||||
<span>
|
<span>
|
||||||
You have used all of your Dyad AI credits this month.{" "}
|
You have used all of your Dyad AI credits this month.{" "}
|
||||||
<ExternalLink href="https://academy.dyad.sh/subscription">
|
<ExternalLink
|
||||||
Upgrade to Dyad Max
|
href="https://academy.dyad.sh/subscription?utm_source=dyad-app&utm_medium=app&utm_campaign=exceeded-budget-error"
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
Reload or upgrade your subscription
|
||||||
</ExternalLink>{" "}
|
</ExternalLink>{" "}
|
||||||
and get more AI credits
|
and get more AI credits
|
||||||
</span>
|
</span>
|
||||||
@@ -80,22 +95,59 @@ export function ChatErrorBox({
|
|||||||
if (error.includes("Fallbacks=")) {
|
if (error.includes("Fallbacks=")) {
|
||||||
error = error.split("Fallbacks=")[0];
|
error = error.split("Fallbacks=")[0];
|
||||||
}
|
}
|
||||||
return <ChatErrorContainer onDismiss={onDismiss}>{error}</ChatErrorContainer>;
|
return (
|
||||||
|
<ChatErrorContainer onDismiss={onDismiss}>
|
||||||
|
{error}
|
||||||
|
<div className="mt-2 space-y-2 space-x-2">
|
||||||
|
<ExternalLink
|
||||||
|
href="https://dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=general-error"
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
Upgrade to Dyad Pro
|
||||||
|
</ExternalLink>
|
||||||
|
|
||||||
|
<ExternalLink href="https://www.dyad.sh/docs/faq">
|
||||||
|
Read docs
|
||||||
|
</ExternalLink>
|
||||||
|
</div>
|
||||||
|
</ChatErrorContainer>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExternalLink({
|
function ExternalLink({
|
||||||
href,
|
href,
|
||||||
children,
|
children,
|
||||||
|
variant = "secondary",
|
||||||
|
icon,
|
||||||
}: {
|
}: {
|
||||||
href: string;
|
href: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
variant?: "primary" | "secondary";
|
||||||
|
icon?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
const baseClasses =
|
||||||
|
"cursor-pointer inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium shadow-sm focus:outline-none focus:ring-2";
|
||||||
|
const primaryClasses =
|
||||||
|
"bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500";
|
||||||
|
const secondaryClasses =
|
||||||
|
"bg-blue-50 text-blue-700 border border-blue-200 hover:bg-blue-100 hover:border-blue-300 focus:ring-blue-200";
|
||||||
|
const iconElement =
|
||||||
|
icon ??
|
||||||
|
(variant === "primary" ? (
|
||||||
|
<CircleArrowUp size={18} />
|
||||||
|
) : (
|
||||||
|
<ExternalLinkIcon size={14} />
|
||||||
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
className="underline cursor-pointer text-blue-500 hover:text-blue-700"
|
className={`${baseClasses} ${
|
||||||
|
variant === "primary" ? primaryClasses : secondaryClasses
|
||||||
|
}`}
|
||||||
onClick={() => IpcClient.getInstance().openExternalUrl(href)}
|
onClick={() => IpcClient.getInstance().openExternalUrl(href)}
|
||||||
>
|
>
|
||||||
{children}
|
<span>{children}</span>
|
||||||
|
{iconElement}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user