Skip to content

Commit

Permalink
chore: bring own api key
Browse files Browse the repository at this point in the history
  • Loading branch information
arshad-yaseen committed Dec 2, 2023
1 parent f675017 commit d4358d5
Show file tree
Hide file tree
Showing 20 changed files with 762 additions and 129 deletions.
13 changes: 6 additions & 7 deletions app/(editor)/edit/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,22 @@ type Markdown = {
markdownPost: {
postCodes: editorCode[]
}
isEligibleForAI: boolean
}

export default function page({ params }: { params: { id: string } }) {
const [editorCodes, setEditorCodes] = useAtom(editorCodesState)
const editorActiveSection = useAtomValue(editorActiveSectionState)
const [markdownCode, setMarkdownCode] = useState("")
const monacoInstance = useAtomValue(monacoInstanceState)
const [isEligibleForAI, setIsEligibleForAI] = useState(true)

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 All @@ -56,8 +57,6 @@ export default function page({ params }: { params: { id: string } }) {
.map((code: editorCode) => {
setMarkdownCode(code.content)
})

setIsEligibleForAI(markdown.isEligibleForAI)
} else {
const defaultCode = [defaultEditorContent] as editorCode[]

Expand Down Expand Up @@ -93,7 +92,7 @@ export default function page({ params }: { params: { id: string } }) {
}}
className="flex h-[92vh] w-full flex-col lg:flex-row"
>
<AIToolsSection isEligibleForAI={isEligibleForAI} />
<AIToolsSection />
<EditorSection
markdown={markdownCode}
onCodeChange={(code) => {
Expand Down
76 changes: 76 additions & 0 deletions app/api/api-key/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { ServerResponse } from "@/server/utils"

import { kvdel, kvgetdec, kvsetenc } from "@/lib/kv"
import { getCurrentUser } from "@/lib/session"

export async function POST(req: Request): Promise<Response> {
try {
const body = await req.json()
const { apiKey } = body

const { sessionUser: user } = await getCurrentUser()

if (!user?.id) {
return ServerResponse.unauthorized()
}

if (!apiKey) {
return ServerResponse.badRequest("Missing API key")
}

await kvsetenc(user.id, "api_key", apiKey)

return ServerResponse.success({
body: { message: "API key saved" },
})
} catch (error) {
return ServerResponse.internalServerError(
error instanceof Error ? error.message : String(error)
)
}
}

export async function GET(): Promise<Response> {
try {
const { sessionUser: user } = await getCurrentUser()

if (!user?.id) {
return ServerResponse.unauthorized()
}

const apiKeyWithModel = (await kvgetdec(user.id, "api_key")) || ""
const apiKey = apiKeyWithModel.split("::")[0]

if (!apiKey) {
return ServerResponse.notFound("API key not found")
}

return ServerResponse.success({
body: { apiKey },
})
} catch (error) {
return ServerResponse.internalServerError(
error instanceof Error ? error.message : String(error)
)
}
}

export async function DELETE(): Promise<Response> {
try {
const { sessionUser: user } = await getCurrentUser()

if (!user?.id) {
return ServerResponse.unauthorized()
}

await kvdel(user.id, "api_key")
return ServerResponse.success({
body: { message: "API key deleted" },
})
} catch (error) {
return ServerResponse.error(
(error as any).message || String(error),
(error as any).status ?? 500
)
}
}
122 changes: 92 additions & 30 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,108 @@
import { ServerResponse } from "@/server/utils"
import { OpenAIBody } from "@/types"
import { isCorrectApiKey } from "@/utils/openai"
import { OpenAIStream, StreamingTextResponse } from "ai"
import OpenAI from "openai"

import { env } from "@/env.mjs"
import { kvget, kvset } from "@/lib/kv"
import { openai } from "@/lib/openai"
import { models } from "@/config/ai"
import { free_credits } from "@/config/subscriptions"
import { kvget, kvgetdec } from "@/lib/kv"
import { getCurrentUser } from "@/lib/session"

if (!env.OPENAI_API_KEY) {
throw new Error("Missing env var from OpenAI")
}
import { getUserSubscriptionPlan } from "@/lib/subscription"

export async function POST(req: Request): Promise<Response> {
// Parse the request body.
const body: OpenAIBody = (await req.json()) as OpenAIBody
const { sessionUser: user } = await getCurrentUser()
try {
const body = await req.json()
const {
openai_body,
type = "chat",
api_key,
stream_response = true,
}: {
openai_body: OpenAIBody
type: "chat" | "vision"
api_key: string
stream_response: boolean
} = body

if (!user?.id) {
return ServerResponse.unauthorized()
}
const { sessionUser: user } = await getCurrentUser()

if (!body.messages) {
return ServerResponse.error("Missing messages in request body")
}
// 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")
const { isPro } = await getUserSubscriptionPlan(user?.id!)

const payload: OpenAI.ChatCompletionCreateParams = {
...body,
model: env.OPENAI_MODEL,
stream: true,
}
// Check if the user has exceeded the free credits limit.
// 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
) {
return ServerResponse.error(
"You have exceeded the free credits limit, please upgrade to pro plan to continue using the AI.",
402
)
}

if (!user?.id) {
return ServerResponse.unauthorized()
}

if (!openai_body) {
return ServerResponse.badRequest("Missing openai_body")
} else if (!openai_body.messages) {
return ServerResponse.badRequest("Missing openai_body.messages")
}

if (!user?.id) {
return ServerResponse.unauthorized()
}

const response = await openai.chat.completions.create(payload)
const user_ai_run_count = await kvget(user.id, "ai_run_count")
// 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]

// Count the number of times the user has used the AI.
kvset(
user.id,
"ai_run_count",
user_ai_run_count ? Number(user_ai_run_count) + 1 : 1
)
let OPENAI_API_KEY

const stream = OpenAIStream(response)
return new StreamingTextResponse(stream)
if (api_key) {
OPENAI_API_KEY = api_key
} else if (api_key_from_kv) {
OPENAI_API_KEY = api_key_from_kv
} else if (env.OPENAI_API_KEY && isPro) {
OPENAI_API_KEY = env.OPENAI_API_KEY
}

if (!OPENAI_API_KEY) {
return ServerResponse.unauthorized("Missing OPENAI_API_KEY")
}

if (!isCorrectApiKey(OPENAI_API_KEY)) {
return ServerResponse.unauthorized("Invalid OPENAI_API_KEY")
}

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

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

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

if (stream_response) {
// @ts-ignore
const stream = OpenAIStream(response)
return new StreamingTextResponse(stream)
} else {
return ServerResponse.success({ body: response })
}
} catch (error) {
return ServerResponse.error(
(error as any).message || String(error),
(error as any).status ?? 500
)
}
}
15 changes: 4 additions & 11 deletions app/api/posts/[markdownId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { ServerResponse } from "@/server/utils"
import * as z from "zod"

import { free_credits } from "@/config/subscriptions"
import { db } from "@/lib/db"
import { kvget, kvset } from "@/lib/kv"
import { kvdel, kvget, kvset } from "@/lib/kv"
import { getCurrentUser } from "@/lib/session"
import { getUserSubscriptionPlan } from "@/lib/subscription"
import { postPatchSchema } from "@/lib/validations/post"

// Set the revalidation interval (currently set to 0, meaning no revalidation).
Expand Down Expand Up @@ -53,9 +51,6 @@ export async function GET(
return ServerResponse.unauthorized()
}

const { isPro: isUserPro } = await getUserSubscriptionPlan(userId)
const user_ai_run_count = await kvget(userId, "ai_run_count")

// Try fetching the post from KV store.
const markdownPostfromKv = await kvget(userId, `markdown-${markdownId}`)
let markdownPost = markdownPostfromKv
Expand Down Expand Up @@ -87,11 +82,6 @@ export async function GET(
return ServerResponse.success({
body: {
markdownPost,
// user_ai_run_count === undefined -- if user starting for first time and cookie not set
isEligibleForAI:
user_ai_run_count === undefined
? true
: isUserPro || Number(user_ai_run_count) <= free_credits,
},
})
} catch (error) {
Expand Down Expand Up @@ -177,6 +167,9 @@ export async function DELETE(
where: { markdownId: params.markdownId },
})

// Delete the post from the KV store.
await kvdel(userId, `markdown-${params.markdownId}`)

return ServerResponse.success({
body: null,
})
Expand Down
8 changes: 8 additions & 0 deletions app/api/webhooks/stripe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Stripe from "stripe"

import { env } from "@/env.mjs"
import { db } from "@/lib/db"
import { kvset } from "@/lib/kv"
import { stripe } from "@/lib/stripe"

export async function POST(req: Request) {
Expand Down Expand Up @@ -56,6 +57,13 @@ export async function POST(req: Request) {
session.subscription as string
)

// set to kv store isPro
try {
await kvset(session?.metadata?.userId!, "isPro", true)
} catch (error) {
console.error("KV set error:", error)
}

// Update the price id and set the new period end.
await db.user.update({
where: {
Expand Down
Loading

0 comments on commit d4358d5

Please sign in to comment.