Show promo messages for relevant AI errors (#216)
This commit is contained in:
128
src/components/chat/ChatErrorBox.tsx
Normal file
128
src/components/chat/ChatErrorBox.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { IpcClient } from "@/ipc/ipc_client";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
export function ChatErrorBox({
|
||||||
|
onDismiss,
|
||||||
|
error,
|
||||||
|
isDyadProEnabled,
|
||||||
|
}: {
|
||||||
|
onDismiss: () => void;
|
||||||
|
error: string;
|
||||||
|
isDyadProEnabled: boolean;
|
||||||
|
}) {
|
||||||
|
if (error.includes("doesn't have a free quota tier")) {
|
||||||
|
return (
|
||||||
|
<ChatErrorContainer onDismiss={onDismiss}>
|
||||||
|
{error}
|
||||||
|
<span className="ml-1">
|
||||||
|
<ExternalLink href="https://dyad.sh/pro">
|
||||||
|
Access with Dyad Pro.
|
||||||
|
</ExternalLink>
|
||||||
|
</span>
|
||||||
|
</ChatErrorContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Important, this needs to come after the "free quota tier" check
|
||||||
|
// because it also includes this URL in the error message
|
||||||
|
if (error.includes("https://ai.google.dev/gemini-api/docs/rate-limits")) {
|
||||||
|
return (
|
||||||
|
<ChatErrorContainer onDismiss={onDismiss}>
|
||||||
|
{error}
|
||||||
|
<span className="ml-1">
|
||||||
|
<ExternalLink href="https://dyad.sh/pro">
|
||||||
|
Upgrade to Dyad Pro.
|
||||||
|
</ExternalLink>
|
||||||
|
</span>
|
||||||
|
</ChatErrorContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.includes("LiteLLM Virtual Key expected")) {
|
||||||
|
return (
|
||||||
|
<ChatInfoContainer onDismiss={onDismiss}>
|
||||||
|
<span>
|
||||||
|
Looks like you don't have a valid Dyad Pro key.{" "}
|
||||||
|
<ExternalLink href="https://dyad.sh/pro">
|
||||||
|
Upgrade to Dyad Pro
|
||||||
|
</ExternalLink>{" "}
|
||||||
|
today.
|
||||||
|
</span>
|
||||||
|
</ChatInfoContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isDyadProEnabled && error.includes("ExceededBudget:")) {
|
||||||
|
return (
|
||||||
|
<ChatInfoContainer onDismiss={onDismiss}>
|
||||||
|
<span>
|
||||||
|
You have used all of your Dyad AI credits this month.{" "}
|
||||||
|
<ExternalLink href="https://academy.dyad.sh/subscription">
|
||||||
|
Upgrade to Dyad Max
|
||||||
|
</ExternalLink>{" "}
|
||||||
|
and get more AI credits
|
||||||
|
</span>
|
||||||
|
</ChatInfoContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <ChatErrorContainer onDismiss={onDismiss}>{error}</ChatErrorContainer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExternalLink({
|
||||||
|
href,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
href: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
className="underline cursor-pointer text-blue-500 hover:text-blue-700"
|
||||||
|
onClick={() => IpcClient.getInstance().openExternalUrl(href)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChatErrorContainer({
|
||||||
|
onDismiss,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
onDismiss: () => void;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="relative mt-2 bg-red-50 border border-red-200 rounded-md shadow-sm p-2 mx-4">
|
||||||
|
<button
|
||||||
|
onClick={onDismiss}
|
||||||
|
className="absolute top-1 left-1 p-1 hover:bg-red-100 rounded"
|
||||||
|
>
|
||||||
|
<X size={14} className="text-red-500" />
|
||||||
|
</button>
|
||||||
|
<div className="px-6 py-1 text-sm">
|
||||||
|
<div className="text-red-700 text-wrap">{children}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChatInfoContainer({
|
||||||
|
onDismiss,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
onDismiss: () => void;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="relative mt-2 bg-sky-50 border border-sky-200 rounded-md shadow-sm p-2 mx-4">
|
||||||
|
<button
|
||||||
|
onClick={onDismiss}
|
||||||
|
className="absolute top-1 left-1 p-1 hover:bg-sky-100 rounded"
|
||||||
|
>
|
||||||
|
<X size={14} className="text-sky-600" />
|
||||||
|
</button>
|
||||||
|
<div className="px-6 py-1 text-sm">
|
||||||
|
<div className="text-sky-800 text-wrap">{children}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -60,6 +60,7 @@ import { AttachmentsList } from "./AttachmentsList";
|
|||||||
import { DragDropOverlay } from "./DragDropOverlay";
|
import { DragDropOverlay } from "./DragDropOverlay";
|
||||||
import { showError, showExtraFilesToast } from "@/lib/toast";
|
import { showError, showExtraFilesToast } from "@/lib/toast";
|
||||||
import { ChatInputControls } from "../ChatInputControls";
|
import { ChatInputControls } from "../ChatInputControls";
|
||||||
|
import { ChatErrorBox } from "./ChatErrorBox";
|
||||||
const showTokenBarAtom = atom(false);
|
const showTokenBarAtom = atom(false);
|
||||||
|
|
||||||
export function ChatInput({ chatId }: { chatId?: number }) {
|
export function ChatInput({ chatId }: { chatId?: number }) {
|
||||||
@@ -235,17 +236,11 @@ export function ChatInput({ chatId }: { chatId?: number }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{error && showError && (
|
{error && showError && (
|
||||||
<div className="relative mt-2 bg-red-50 border border-red-200 rounded-md shadow-sm p-2">
|
<ChatErrorBox
|
||||||
<button
|
onDismiss={dismissError}
|
||||||
onClick={dismissError}
|
error={error}
|
||||||
className="absolute top-1 left-1 p-1 hover:bg-red-100 rounded"
|
isDyadProEnabled={settings.enableDyadPro ?? false}
|
||||||
>
|
/>
|
||||||
<X size={14} className="text-red-500" />
|
|
||||||
</button>
|
|
||||||
<div className="px-6 py-1 text-sm">
|
|
||||||
<div className="text-red-700 text-wrap">{error}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{/* Display loading or error state for proposal */}
|
{/* Display loading or error state for proposal */}
|
||||||
{isProposalLoading && (
|
{isProposalLoading && (
|
||||||
|
|||||||
Reference in New Issue
Block a user