Spaces:
Running
Running
show error for free limits
Browse files- app/api/ask/route.ts +15 -5
- components/ask-ai/useGeneration.ts +13 -6
- components/chat/index.tsx +21 -6
- components/pro-modal/index.tsx +100 -0
- components/ui/button.tsx +1 -0
- lib/type.ts +4 -3
app/api/ask/route.ts
CHANGED
|
@@ -107,16 +107,26 @@ export async function POST(request: Request) {
|
|
| 107 |
|
| 108 |
await writer.close();
|
| 109 |
} catch (error) {
|
| 110 |
-
console.error(error);
|
| 111 |
try {
|
| 112 |
const errorMessage =
|
| 113 |
error instanceof Error
|
| 114 |
? error.message
|
| 115 |
: "An error occurred while processing your request";
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
await writer.write(encoder.encode(`\n\n__ERROR__:${errorPayload}`));
|
| 121 |
await writer.close();
|
| 122 |
} catch (closeError) {
|
|
|
|
| 107 |
|
| 108 |
await writer.close();
|
| 109 |
} catch (error) {
|
|
|
|
| 110 |
try {
|
| 111 |
const errorMessage =
|
| 112 |
error instanceof Error
|
| 113 |
? error.message
|
| 114 |
: "An error occurred while processing your request";
|
| 115 |
+
let errorPayload = "";
|
| 116 |
+
if (
|
| 117 |
+
errorMessage?.includes("exceeded your monthly included credits")
|
| 118 |
+
) {
|
| 119 |
+
errorPayload = JSON.stringify({
|
| 120 |
+
messageError: errorMessage,
|
| 121 |
+
showProMessage: true,
|
| 122 |
+
isError: true,
|
| 123 |
+
});
|
| 124 |
+
} else {
|
| 125 |
+
errorPayload = JSON.stringify({
|
| 126 |
+
messageError: errorMessage,
|
| 127 |
+
isError: true,
|
| 128 |
+
});
|
| 129 |
+
}
|
| 130 |
await writer.write(encoder.encode(`\n\n__ERROR__:${errorPayload}`));
|
| 131 |
await writer.close();
|
| 132 |
} catch (closeError) {
|
components/ask-ai/useGeneration.ts
CHANGED
|
@@ -326,11 +326,6 @@ export const useGeneration = (projectName: string) => {
|
|
| 326 |
setIsLoading(false);
|
| 327 |
return;
|
| 328 |
}
|
| 329 |
-
// if (abortController.current?.signal.aborted) {
|
| 330 |
-
// toast.error("Generation aborted");
|
| 331 |
-
// setIsLoading(false);
|
| 332 |
-
// return;
|
| 333 |
-
// }
|
| 334 |
const chunk = decoder.decode(value, { stream: true });
|
| 335 |
completeResponse += chunk;
|
| 336 |
|
|
@@ -339,13 +334,25 @@ export const useGeneration = (projectName: string) => {
|
|
| 339 |
if (errorMatch) {
|
| 340 |
try {
|
| 341 |
const errorData = JSON.parse(errorMatch[1]);
|
|
|
|
| 342 |
if (errorData.isError) {
|
| 343 |
const lastMessageId =
|
| 344 |
currentMessages[currentMessages.length - 1].id;
|
| 345 |
updateMessage(lastMessageId, {
|
| 346 |
isThinking: false,
|
| 347 |
isAborted: true,
|
| 348 |
-
content:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
});
|
| 350 |
setIsLoading(false);
|
| 351 |
return;
|
|
|
|
| 326 |
setIsLoading(false);
|
| 327 |
return;
|
| 328 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
const chunk = decoder.decode(value, { stream: true });
|
| 330 |
completeResponse += chunk;
|
| 331 |
|
|
|
|
| 334 |
if (errorMatch) {
|
| 335 |
try {
|
| 336 |
const errorData = JSON.parse(errorMatch[1]);
|
| 337 |
+
console.log("Parsed error data:", errorData);
|
| 338 |
if (errorData.isError) {
|
| 339 |
const lastMessageId =
|
| 340 |
currentMessages[currentMessages.length - 1].id;
|
| 341 |
updateMessage(lastMessageId, {
|
| 342 |
isThinking: false,
|
| 343 |
isAborted: true,
|
| 344 |
+
content: errorData?.showProMessage
|
| 345 |
+
? `You have exceeded your monthly included credits with Hugging Face inference provider. Please consider upgrading to a pro plan.`
|
| 346 |
+
: `Error: ${errorData.messageError}`,
|
| 347 |
+
actions: errorData?.showProMessage
|
| 348 |
+
? [
|
| 349 |
+
{
|
| 350 |
+
label: "Upgrade to Pro",
|
| 351 |
+
variant: "pro",
|
| 352 |
+
type: MessageActionType.UPGRADE_TO_PRO,
|
| 353 |
+
},
|
| 354 |
+
]
|
| 355 |
+
: [],
|
| 356 |
});
|
| 357 |
setIsLoading(false);
|
| 358 |
return;
|
components/chat/index.tsx
CHANGED
|
@@ -2,7 +2,8 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
| 2 |
import { useSession } from "next-auth/react";
|
| 3 |
import { cn } from "@/lib/utils";
|
| 4 |
import { ChevronRight, ExternalLink } from "lucide-react";
|
| 5 |
-
import { useEffect, useRef } from "react";
|
|
|
|
| 6 |
import Markdown from "react-markdown";
|
| 7 |
import { useQueryClient } from "@tanstack/react-query";
|
| 8 |
import { formatDistanceToNow } from "date-fns";
|
|
@@ -11,12 +12,14 @@ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
|
| 11 |
import { dracula } from "react-syntax-highlighter/dist/esm/styles/prism";
|
| 12 |
|
| 13 |
import { useChat } from "./useChat";
|
| 14 |
-
import { AiLoading } from "
|
| 15 |
-
import Loading from "
|
| 16 |
-
import { useGeneration } from "
|
| 17 |
import { MessageAction, MessageActionType, File } from "@/lib/type";
|
| 18 |
-
import { Button } from "
|
| 19 |
-
import { getFileIcon } from "
|
|
|
|
|
|
|
| 20 |
|
| 21 |
export function AppEditorChat({
|
| 22 |
isNew,
|
|
@@ -33,6 +36,7 @@ export function AppEditorChat({
|
|
| 33 |
const chatProjectName = isNew ? "new" : projectName ?? "new";
|
| 34 |
const { messages } = useChat(chatProjectName);
|
| 35 |
const { isLoading, createProject } = useGeneration(chatProjectName);
|
|
|
|
| 36 |
|
| 37 |
const project = queryClient.getQueryData<SpaceEntry>(["project"]);
|
| 38 |
const files = queryClient.getQueryData<File[]>(["files"]) ?? [];
|
|
@@ -49,6 +53,7 @@ export function AppEditorChat({
|
|
| 49 |
if (!action) return;
|
| 50 |
switch (action.type) {
|
| 51 |
case MessageActionType.PUBLISH_PROJECT:
|
|
|
|
| 52 |
return createProject(
|
| 53 |
files ?? [],
|
| 54 |
action.projectTitle ?? "",
|
|
@@ -60,6 +65,8 @@ export function AppEditorChat({
|
|
| 60 |
`https://huggingface.co/spaces/${project?.name}`,
|
| 61 |
"_blank"
|
| 62 |
);
|
|
|
|
|
|
|
| 63 |
}
|
| 64 |
};
|
| 65 |
|
|
@@ -295,6 +302,13 @@ export function AppEditorChat({
|
|
| 295 |
overlay={false}
|
| 296 |
/>
|
| 297 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
{action.label}
|
| 299 |
</Button>
|
| 300 |
))}
|
|
@@ -304,6 +318,7 @@ export function AppEditorChat({
|
|
| 304 |
</div>
|
| 305 |
))}
|
| 306 |
</div>
|
|
|
|
| 307 |
</div>
|
| 308 |
);
|
| 309 |
}
|
|
|
|
| 2 |
import { useSession } from "next-auth/react";
|
| 3 |
import { cn } from "@/lib/utils";
|
| 4 |
import { ChevronRight, ExternalLink } from "lucide-react";
|
| 5 |
+
import { useEffect, useRef, useState } from "react";
|
| 6 |
+
import Image from "next/image";
|
| 7 |
import Markdown from "react-markdown";
|
| 8 |
import { useQueryClient } from "@tanstack/react-query";
|
| 9 |
import { formatDistanceToNow } from "date-fns";
|
|
|
|
| 12 |
import { dracula } from "react-syntax-highlighter/dist/esm/styles/prism";
|
| 13 |
|
| 14 |
import { useChat } from "./useChat";
|
| 15 |
+
import { AiLoading } from "@/components/ask-ai/loading";
|
| 16 |
+
import Loading from "@/components/loading";
|
| 17 |
+
import { useGeneration } from "@/components/ask-ai/useGeneration";
|
| 18 |
import { MessageAction, MessageActionType, File } from "@/lib/type";
|
| 19 |
+
import { Button } from "@/components/ui/button";
|
| 20 |
+
import { getFileIcon } from "@/components/ask-ai/input-mentions";
|
| 21 |
+
import ProIcon from "@/assets/pro.svg";
|
| 22 |
+
import ProModal from "../pro-modal";
|
| 23 |
|
| 24 |
export function AppEditorChat({
|
| 25 |
isNew,
|
|
|
|
| 36 |
const chatProjectName = isNew ? "new" : projectName ?? "new";
|
| 37 |
const { messages } = useChat(chatProjectName);
|
| 38 |
const { isLoading, createProject } = useGeneration(chatProjectName);
|
| 39 |
+
const [openProModal, setOpenProModal] = useState(false);
|
| 40 |
|
| 41 |
const project = queryClient.getQueryData<SpaceEntry>(["project"]);
|
| 42 |
const files = queryClient.getQueryData<File[]>(["files"]) ?? [];
|
|
|
|
| 53 |
if (!action) return;
|
| 54 |
switch (action.type) {
|
| 55 |
case MessageActionType.PUBLISH_PROJECT:
|
| 56 |
+
const files = queryClient.getQueryData<File[]>(["files"]) ?? [];
|
| 57 |
return createProject(
|
| 58 |
files ?? [],
|
| 59 |
action.projectTitle ?? "",
|
|
|
|
| 65 |
`https://huggingface.co/spaces/${project?.name}`,
|
| 66 |
"_blank"
|
| 67 |
);
|
| 68 |
+
case MessageActionType.UPGRADE_TO_PRO:
|
| 69 |
+
return setOpenProModal(true);
|
| 70 |
}
|
| 71 |
};
|
| 72 |
|
|
|
|
| 302 |
overlay={false}
|
| 303 |
/>
|
| 304 |
)}
|
| 305 |
+
{action.type === MessageActionType.UPGRADE_TO_PRO && (
|
| 306 |
+
<Image
|
| 307 |
+
src={ProIcon}
|
| 308 |
+
alt="Pro Icon"
|
| 309 |
+
className="size-3.5"
|
| 310 |
+
/>
|
| 311 |
+
)}
|
| 312 |
{action.label}
|
| 313 |
</Button>
|
| 314 |
))}
|
|
|
|
| 318 |
</div>
|
| 319 |
))}
|
| 320 |
</div>
|
| 321 |
+
<ProModal open={openProModal} onClose={setOpenProModal} />
|
| 322 |
</div>
|
| 323 |
);
|
| 324 |
}
|
components/pro-modal/index.tsx
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useLocalStorage } from "react-use";
|
| 2 |
+
import { Button } from "@/components/ui/button";
|
| 3 |
+
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
| 4 |
+
import { CheckCheck } from "lucide-react";
|
| 5 |
+
|
| 6 |
+
export const ProModal = ({
|
| 7 |
+
open,
|
| 8 |
+
onClose,
|
| 9 |
+
}: {
|
| 10 |
+
open: boolean;
|
| 11 |
+
onClose: React.Dispatch<React.SetStateAction<boolean>>;
|
| 12 |
+
}) => {
|
| 13 |
+
const handleProClick = () => {
|
| 14 |
+
window.open("https://huggingface.co/subscribe/pro?from=DeepSite", "_blank");
|
| 15 |
+
onClose(false);
|
| 16 |
+
};
|
| 17 |
+
return (
|
| 18 |
+
<Dialog open={open} onOpenChange={onClose}>
|
| 19 |
+
<DialogContent
|
| 20 |
+
showCloseButton={false}
|
| 21 |
+
className="sm:max-w-[425px] rounded-3xl! p-0! overflow-hidden"
|
| 22 |
+
>
|
| 23 |
+
<DialogTitle className="hidden" />
|
| 24 |
+
<header className="bg-linear-to-b from-indigo-500/25 dark:from-indigo-500/40 to-background px-6 pt-6">
|
| 25 |
+
<div className="flex items-center justify-start -space-x-4 mb-5">
|
| 26 |
+
<div className="size-14 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-3xl opacity-50">
|
| 27 |
+
🚀
|
| 28 |
+
</div>
|
| 29 |
+
<div className="size-16 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-4xl z-2">
|
| 30 |
+
🤩
|
| 31 |
+
</div>
|
| 32 |
+
<div className="size-14 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-3xl opacity-50">
|
| 33 |
+
🥳
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
<h2 className="text-2xl font-bold text-primary">
|
| 37 |
+
Only $9 to enhance your possibilities
|
| 38 |
+
</h2>
|
| 39 |
+
<p className="text-muted-foreground text-base mt-2 max-w-sm">
|
| 40 |
+
It seems like you have reached the monthly free limit of DeepSite.
|
| 41 |
+
</p>
|
| 42 |
+
</header>
|
| 43 |
+
<main className="flex flex-col items-start text-left relative px-6 pb-6">
|
| 44 |
+
<div className="w-1/2 h-px bg-accent/70 my-4"></div>
|
| 45 |
+
<p className="text-lg mt-3 text-primary font-semibold">
|
| 46 |
+
Upgrade to a <ProTag className="mx-1" /> Account, and unlock your
|
| 47 |
+
DeepSite high quota access ⚡
|
| 48 |
+
</p>
|
| 49 |
+
<ul className="mt-3 space-y-1 text-muted-foreground">
|
| 50 |
+
<li className="text-sm text-muted-foreground space-x-2 flex items-center justify-start gap-2 mb-3">
|
| 51 |
+
You'll also unlock some Hugging Face PRO features, like:
|
| 52 |
+
</li>
|
| 53 |
+
<li className="text-sm space-x-2 flex items-center justify-start gap-2">
|
| 54 |
+
<CheckCheck className="text-emerald-500 size-4" />
|
| 55 |
+
Get acces to thousands of AI app (ZeroGPU) with high quota
|
| 56 |
+
</li>
|
| 57 |
+
<li className="text-sm space-x-2 flex items-center justify-start gap-2">
|
| 58 |
+
<CheckCheck className="text-emerald-500 size-4" />
|
| 59 |
+
Get exclusive early access to new features and updates
|
| 60 |
+
</li>
|
| 61 |
+
<li className="text-sm space-x-2 flex items-center justify-start gap-2">
|
| 62 |
+
<CheckCheck className="text-emerald-500 size-4" />
|
| 63 |
+
Get free credits across all Inference Providers
|
| 64 |
+
</li>
|
| 65 |
+
<li className="text-sm text-muted-foreground space-x-2 flex items-center justify-start gap-2 mt-3">
|
| 66 |
+
... and lots more!
|
| 67 |
+
</li>
|
| 68 |
+
</ul>
|
| 69 |
+
<Button
|
| 70 |
+
variant="default"
|
| 71 |
+
size="lg"
|
| 72 |
+
tabIndex={-1}
|
| 73 |
+
className="w-full mt-8"
|
| 74 |
+
onClick={handleProClick}
|
| 75 |
+
>
|
| 76 |
+
Subscribe to PRO ($9/month)
|
| 77 |
+
</Button>
|
| 78 |
+
</main>
|
| 79 |
+
</DialogContent>
|
| 80 |
+
</Dialog>
|
| 81 |
+
);
|
| 82 |
+
};
|
| 83 |
+
|
| 84 |
+
export const ProTag = ({
|
| 85 |
+
className,
|
| 86 |
+
...props
|
| 87 |
+
}: {
|
| 88 |
+
className?: string;
|
| 89 |
+
onClick?: () => void;
|
| 90 |
+
}) => (
|
| 91 |
+
<span
|
| 92 |
+
className={`${className} ${
|
| 93 |
+
props.onClick ? "cursor-pointer" : ""
|
| 94 |
+
} bg-linear-to-br shadow-green-500/10 dark:shadow-green-500/20 inline-block -skew-x-12 from-pink-500 via-green-400 to-yellow-400 text-xs font-bold text-black shadow-lg rounded-md px-2.5 py-[3px]`}
|
| 95 |
+
{...props}
|
| 96 |
+
>
|
| 97 |
+
PRO
|
| 98 |
+
</span>
|
| 99 |
+
);
|
| 100 |
+
export default ProModal;
|
components/ui/button.tsx
CHANGED
|
@@ -27,6 +27,7 @@ const buttonVariants = cva(
|
|
| 27 |
"border border-indigo-500 bg-indigo-500 text-white hover:bg-indigo-600 dark:border-indigo-500/30 dark:bg-indigo-500/20 dark:text-indigo-400 dark:hover:bg-indigo-500/30",
|
| 28 |
"ghost-bordered":
|
| 29 |
"border bg-primary-foreground hover:bg-background hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
|
|
| 30 |
},
|
| 31 |
size: {
|
| 32 |
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
|
|
| 27 |
"border border-indigo-500 bg-indigo-500 text-white hover:bg-indigo-600 dark:border-indigo-500/30 dark:bg-indigo-500/20 dark:text-indigo-400 dark:hover:bg-indigo-500/30",
|
| 28 |
"ghost-bordered":
|
| 29 |
"border bg-primary-foreground hover:bg-background hover:text-accent-foreground dark:hover:bg-accent/50",
|
| 30 |
+
pro: "bg-linear-to-br from-pink-500 dark:from-pink-500/50 via-green-500 dark:via-green-500/50 to-amber-500 dark:to-amber-500/50 text-white hover:brightness-120 font-semibold! [&_img]:grayscale [&_img]:brightness-1 [&_img]:invert dark:[&_img]:invert-0 dark:[&_img]:grayscale-0 dark:[&_img]:brightness-100",
|
| 31 |
},
|
| 32 |
size: {
|
| 33 |
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
lib/type.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type ProviderType = "auto" | "cheapest" | "fastest" | string;
|
|
| 9 |
export enum MessageActionType {
|
| 10 |
PUBLISH_PROJECT = "PUBLISH_PROJECT",
|
| 11 |
SEE_LIVE_PREVIEW = "SEE_LIVE_PREVIEW",
|
|
|
|
| 12 |
}
|
| 13 |
export type MessageAction = {
|
| 14 |
label: string;
|
|
@@ -20,7 +21,7 @@ export type MessageAction = {
|
|
| 20 |
projectTitle?: string;
|
| 21 |
prompt?: string;
|
| 22 |
messageReferrer?: number;
|
| 23 |
-
}
|
| 24 |
export type Message = {
|
| 25 |
id: string;
|
| 26 |
role: MessageRole;
|
|
@@ -35,11 +36,11 @@ export type Message = {
|
|
| 35 |
};
|
| 36 |
export type File = {
|
| 37 |
path: string;
|
| 38 |
-
content?: string
|
| 39 |
};
|
| 40 |
export interface Commit {
|
| 41 |
title: string;
|
| 42 |
oid: string;
|
| 43 |
date: Date;
|
| 44 |
}
|
| 45 |
-
export type MessageRole = "user" | "assistant";
|
|
|
|
| 9 |
export enum MessageActionType {
|
| 10 |
PUBLISH_PROJECT = "PUBLISH_PROJECT",
|
| 11 |
SEE_LIVE_PREVIEW = "SEE_LIVE_PREVIEW",
|
| 12 |
+
UPGRADE_TO_PRO = "UPGRADE_TO_PRO",
|
| 13 |
}
|
| 14 |
export type MessageAction = {
|
| 15 |
label: string;
|
|
|
|
| 21 |
projectTitle?: string;
|
| 22 |
prompt?: string;
|
| 23 |
messageReferrer?: number;
|
| 24 |
+
};
|
| 25 |
export type Message = {
|
| 26 |
id: string;
|
| 27 |
role: MessageRole;
|
|
|
|
| 36 |
};
|
| 37 |
export type File = {
|
| 38 |
path: string;
|
| 39 |
+
content?: string;
|
| 40 |
};
|
| 41 |
export interface Commit {
|
| 42 |
title: string;
|
| 43 |
oid: string;
|
| 44 |
date: Date;
|
| 45 |
}
|
| 46 |
+
export type MessageRole = "user" | "assistant";
|