Skip to content

Commit

Permalink
chore: finished
Browse files Browse the repository at this point in the history
  • Loading branch information
arshad-yaseen committed Dec 3, 2023
1 parent 96927c8 commit 869d0fe
Show file tree
Hide file tree
Showing 16 changed files with 103 additions and 77 deletions.
4 changes: 2 additions & 2 deletions app/(editor)/edit/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ export default function page({ params }: { params: { id: string } }) {
const markdownId = params.id

const getMarkdownPost = async (markdownId: string) => {
const markdown = await GET<Markdown>(`/api/posts/${markdownId}`,{
const markdown = await GET<Markdown>(`/api/posts/${markdownId}`, {
showErrorToast: true,
error: "Error fetching markdown post",
})

const markdownPost = markdown.markdownPost

if (
Expand Down
36 changes: 27 additions & 9 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import OpenAI from "openai"
import { env } from "@/env.mjs"
import { models } from "@/config/ai"
import { free_credits } from "@/config/subscriptions"
import { kvget, kvgetdec } from "@/lib/kv"
import { kvget, kvgetdec, kvset } from "@/lib/kv"
import { getCurrentUser } from "@/lib/session"
import { getUserSubscriptionPlan } from "@/lib/subscription"

Expand All @@ -32,10 +32,10 @@ export async function POST(req: Request): Promise<Response> {
return ServerResponse.unauthorized()
}

// Get the User provided API Key and API key compatible OpenAI model from KV store.
const api_key_with_model_from_kv = await kvgetdec(user.id, "api_key")
const api_key_from_kv = api_key_with_model_from_kv?.split("::")[0]
const model_from_kv = api_key_with_model_from_kv?.split("::")[1]
// Get the User provided API Key and API key compatible OpenAI model from KV store.
const api_key_with_model_from_kv = await kvgetdec(user.id, "api_key")
const api_key_from_kv = api_key_with_model_from_kv?.split("::")[0]
const model_from_kv = api_key_with_model_from_kv?.split("::")[1]

// The count of the number of times the user has used the AI.
const user_ai_run_count = await kvget(user?.id!, "ai_run_count")
Expand All @@ -45,8 +45,10 @@ export async function POST(req: Request): Promise<Response> {
// user_ai_run_count !== undefined -- if user generating openai chat first time and cookie not set
if (
user_ai_run_count !== undefined &&
Number(user_ai_run_count) > free_credits &&
!isPro && !api_key_from_kv && !api_key
Number(user_ai_run_count) >= free_credits &&
!isPro &&
!api_key_from_kv &&
!api_key
) {
return ServerResponse.error(
"You have exceeded the free credits limit, please upgrade to pro plan to continue using the AI.",
Expand All @@ -72,6 +74,9 @@ export async function POST(req: Request): Promise<Response> {
OPENAI_API_KEY = api_key
} else if (api_key_from_kv) {
OPENAI_API_KEY = api_key_from_kv
} else if (user_ai_run_count === null) {
// if user generating openai chat first time and cookie not set
OPENAI_API_KEY = env.OPENAI_API_KEY
}

if (!OPENAI_API_KEY) {
Expand All @@ -81,17 +86,30 @@ export async function POST(req: Request): Promise<Response> {
if (!isCorrectApiKey(OPENAI_API_KEY)) {
return ServerResponse.unauthorized("Invalid OPENAI_API_KEY")
}


console.log(OPENAI_API_KEY)

const openai = new OpenAI({ apiKey: OPENAI_API_KEY })

const payload: OpenAI.ChatCompletionCreateParams = {
...openai_body,
model: model_from_kv ? model_from_kv : type === "chat" ? models.chat : models.vision,
model: model_from_kv
? model_from_kv
: type === "chat"
? models.chat
: models.vision,
stream: stream_response,
}

const response = await openai.chat.completions.create(payload)

// Increment the count of the number of times the user has used the AI.
await kvset(
user?.id!,
"ai_run_count",
!user_ai_run_count ? 1 : Number(user_ai_run_count) + 1
)

if (stream_response) {
// @ts-ignore
const stream = OpenAIStream(response)
Expand Down
2 changes: 1 addition & 1 deletion app/api/webhooks/stripe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function POST(req: Request) {
env.STRIPE_WEBHOOK_SECRET || ""
)
} catch (error) {
console.log(error)
console.error(error)

return ServerResponse.error("Webhook Error")
}
Expand Down
77 changes: 35 additions & 42 deletions components/bring-api-key.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useEffect, useState } from "react"
import { OPENAI_USAGE_POLICIES } from "@/constants/links"
import { DELETE, GET, POST } from "@/utils/http.utils"
import { isCorrectApiKey, validateApiKey } from "@/utils/openai"
import { ArrowTopRightIcon } from "@radix-ui/react-icons"
import { KeyIcon, Loader2Icon, Trash2Icon } from "lucide-react"
import { KeyIcon, Loader2Icon } from "lucide-react"
import { toast } from "sonner"

import { models } from "@/config/ai"
import { cn } from "@/lib/utils"
import { Button, ButtonProps } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
Expand All @@ -18,7 +20,6 @@ import {
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { cn } from "@/lib/utils"

const BringApiKey = (props: ButtonProps) => {
const [apiKey, setApiKey] = useState<string>("")
Expand All @@ -42,40 +43,29 @@ const BringApiKey = (props: ButtonProps) => {

if (isValid.error) {
if (isValid.statusCode === 404) {
console.log("Hello 2", models.chat_old);
console.log("Is valid", isValid);

await save(models.chat_old)
} else {
toast.error(isValid.message)
}
setSaving(false)
} else {
console.log("Hello 3", models.chat);

await save(models.chat)
}
}

const save = async (model: string) => {
console.log("Hello", model);

const keyToSave = `${apiKey}::${model}`
const res = await fetch("/api/api-key", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ apiKey: keyToSave }),
})

setSaving(false)

if (!res.ok) {
toast.error("API key not saved")
return
}
await POST(
"/api/api-key",
{ apiKey: keyToSave },
{
error: "API key not saved",
showErrorToast: true,
}
)

setSaving(false)
toast.success("API key saved successfully")
setIsDialogOpen(false)
setIsApiKeyFromSession(true)
Expand All @@ -84,14 +74,13 @@ const BringApiKey = (props: ButtonProps) => {
const handleDelete = async () => {
if (deleting) return
setDeleting(true)
const res = await fetch("/api/api-key", {
method: "DELETE",

await DELETE("/api/api-key", {
error: "API key not deleted",
showErrorToast: true,
})

setDeleting(false)
if (!res.ok) {
toast.error("API key not deleted")
return
}

toast.success("API key deleted successfully")
reset()
Expand All @@ -108,22 +97,25 @@ const BringApiKey = (props: ButtonProps) => {
}

useEffect(() => {
fetch("/api/api-key")
.then((res) => res.json())
.then((res) => {
if (res.apiKey) {
setApiKey(res.apiKey)
setAccepted(true)
setIsApiKeyFromSession(true)
}
})
getApiKeyFromSession()
}, [])

const getApiKeyFromSession = async () => {
const key_res = await GET<{ apiKey: string }>("/api/api-key", {
error: "Could not fetch API key",
})
if (key_res.apiKey) {
setAccepted(true)
setApiKey(key_res.apiKey)
setIsApiKeyFromSession(true)
}
}

return (
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button className={cn("w-full", props.className)} {...props}>
<KeyIcon className="inline-block h-4 w-4 mr-2" />
<KeyIcon className="mr-2 inline-block h-4 w-4" />
{isApiKeyFromSession ? "Change" : "Bring"} OpenAI API Key
</Button>
</DialogTrigger>
Expand All @@ -133,7 +125,7 @@ const BringApiKey = (props: ButtonProps) => {
Your own OpenAI API Key
</DialogTitle>
<DialogDescription>
<p className="text-muted-foreground text-center tracking-tight">
<p className="text-center tracking-tight text-muted-foreground">
You need to bring your OpenAI API key to use AI.
</p>
</DialogDescription>
Expand Down Expand Up @@ -182,10 +174,11 @@ const BringApiKey = (props: ButtonProps) => {
</div>
{isSecureOpen && (
<div className="flex flex-col space-y-5">
<p className="text-muted-foreground text-sm">
Your API key is stored exclusively in the session store and is encrypted, guaranteeing maximum security and privacy.
<p className="text-sm text-muted-foreground">
Your API key is stored exclusively in the session store and is
encrypted, guaranteeing maximum security and privacy.
</p>
<p className="text-muted-foreground text-sm">
<p className="text-sm text-muted-foreground">
Our website is open source. Check out the code to see how we
protect your API key!
</p>
Expand Down
4 changes: 2 additions & 2 deletions components/dashboard/dashboard-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import BringApiKey from "@/components/bring-api-key"

import SiteAssets from "../site-assets"
import { UserAvatar } from "./user-avatar"
import BringApiKey from "@/components/bring-api-key"

interface UserAccountNavProps extends React.HTMLAttributes<HTMLDivElement> {
user: Pick<User, "name" | "image" | "email">
Expand All @@ -33,7 +33,7 @@ function DashboardHeader({ user }: UserAccountNavProps) {
</div>

<div className={`flex h-full w-1/2 items-center justify-end`}>
<BringApiKey className="w-fit mr-6" variant={"outline"} />
<BringApiKey className="mr-6 w-fit" variant={"outline"} />
<DropdownMenu>
<DropdownMenuTrigger>
<UserAvatar
Expand Down
2 changes: 1 addition & 1 deletion components/editor/ai-tools-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function AIToolsSection() {
<>
<div
className={`relative hidden h-full ${
isToolsPanelCollapsed ? "invisible w-0 opacity-0" : "min-w-[19%]"
isToolsPanelCollapsed ? "invisible w-0 opacity-0" : "min-w-[18%]"
} flex-col items-center border-r p-6 lg:flex `}
>
<Tabs defaultValue="tools" className="w-full">
Expand Down
2 changes: 1 addition & 1 deletion components/editor/ai-tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function AITools() {

return (
<>
<div className="no-scrollbar max-h-[51vh] space-y-4 overflow-y-scroll">
<div className="no-scrollbar max-h-[51vh] space-y-4 overflow-y-scroll p-1">
<Button
onClick={() => {
handleClick({
Expand Down
26 changes: 22 additions & 4 deletions components/editor/ask-ai.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import { ChatScrollAnchor } from "../chat-scroll-anchor"
import CodeBlock from "../code-block"
import { Button } from "../ui/button"
import { Input } from "../ui/input"
import ParseMarkdown from "./parse-markdown"
import UpgradeToPRODialog from "../upgrade-to-pro-dialog"
import ParseMarkdown from "./parse-markdown"

function AskAI() {
const [isDialogOpen, setIsDialogOpen] = useState(false)
Expand Down Expand Up @@ -117,12 +117,11 @@ function AskAI() {

// Handle any errors returned by the API
if (res?.error && res?.error.statusCode === 402) {
setRequestingToAPI(false)
setUpgradeToPRODialog(true)
reset()
return
} else if (res?.error?.message) {
toast.error(res?.error.message)
setRequestingToAPI(false)
reset()
return
}

Expand Down Expand Up @@ -162,6 +161,25 @@ function AskAI() {
}
}

const reset = () => {
setRequestingToAPI(false)
setUpgradeToPRODialog(true)
setGenerating(false)
stopGenerating.current = false
setMessages([])
setChats([
{
id: 1,
user: `${
editorSelectedContent
? "How can i help you with this markdown?"
: "Hello, How can i help you?"
}`,
ai: "",
},
])
}

const clearChat = () => {
setChats([
{
Expand Down
2 changes: 1 addition & 1 deletion components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const buttonVariants = cva(
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"shadow-border-small bg-transparent hover:bg-accent hover:text-accent-foreground",
"shadow-border-small bg-transparent hover:bg-accent/50 hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
Expand Down
2 changes: 1 addition & 1 deletion components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-lg border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background transition-[border-color,box-shadow,background-color] placeholder:text-muted-foreground focus:border-black/40 focus:outline-none focus:ring-[3px] focus:ring-muted-foreground/20 disabled:cursor-not-allowed disabled:opacity-50 dark:ring-white/30 dark:focus:border-white/70",
"flex h-10 w-full items-center justify-between rounded-lg bg-transparent px-3 py-2 text-sm shadow-border-small ring-offset-background transition-[border-color,box-shadow,background-color] duration-200 placeholder:text-muted-foreground hover:shadow-border-medium focus:border-black/40 focus:outline-none focus:ring-[3px] focus:ring-muted-foreground/20 disabled:cursor-not-allowed disabled:opacity-50 dark:ring-white/30 dark:focus:border-white/70",
className
)}
{...props}
Expand Down
4 changes: 2 additions & 2 deletions components/upgrade-to-pro-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ function UpgradeToPRODialog({
plan for unlimited AI access.
</DialogDescription>

<div className="mt-6 flex flex-col w-full items-center space-x-2">
<div className="mt-6 flex w-full flex-col items-center space-x-2">
<Button
onClick={() => router.push("/dashboard/billing")}
className="w-full"
>
Upgrade
</Button>
<span className="py-1.5">or</span>
<BringApiKey variant={"outline"} />
<BringApiKey variant={"outline"} />
</div>
</DialogContent>
</Dialog>
Expand Down
2 changes: 1 addition & 1 deletion config/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ export const example_chat_api_messages = [
{
role: "user",
content: "Hey",
}
},
]
Loading

0 comments on commit 869d0fe

Please sign in to comment.