diff --git a/apps/www/src/components/Chat.tsx b/apps/www/src/components/Chat.tsx index 40a94076..e7149802 100644 --- a/apps/www/src/components/Chat.tsx +++ b/apps/www/src/components/Chat.tsx @@ -9,6 +9,7 @@ import { SelectTrigger, } from "@/components/ui/Select"; import { Icons } from "@/icons"; +import { getMaskedKey } from "@/lib/maskKey"; import { cn } from "@/lib/utils"; import { nanoid, type Message } from "ai"; import { useChat } from "ai/react"; @@ -57,6 +58,7 @@ export const Chat = () => { const [selectedChatGptModel, setSelectedChatGptModel] = React.useState(CHAT_GPT_MODELS[0]); const [systemMessage, setSystemMessage] = React.useState(""); + const [error, setError] = React.useState(); const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ api: "/api/chat", @@ -74,6 +76,9 @@ export const Chat = () => { apiKey: currentApiKey, model: selectedChatGptModel, }, + onError: (error: Error) => { + setError(JSON.parse(error.message)); + }, }); const scrollToBottom = () => { @@ -85,6 +90,8 @@ export const Chat = () => { const handleUpdateApiKey = (e: React.ChangeEvent) => { const newApiKey = e.target.value; setCurrentApiKey(newApiKey); + + e.currentTarget.blur(); }; const handleUpdateChatGptModel = (value: string) => { @@ -92,6 +99,7 @@ export const Chat = () => { }; const onSubmit = (e: React.FormEvent) => { + setError(undefined); storage?.setItem(CHAT_OPENAI_API_KEY, currentApiKey); scrollToBottom(); handleSubmit(e); @@ -119,10 +127,24 @@ export const Chat = () => { { + if (!((e.ctrlKey || e.metaKey) && e.key === "v")) { + e.preventDefault(); + } + }} + onFocus={(e) => { + e.currentTarget.select(); + }} className="focus-within:border-white" - placeholder="Enter Your API Key" + placeholder="Paste Your API Key" onChange={handleUpdateApiKey} + onDragStart={(e) => e.preventDefault()} + onDragOver={(e) => e.preventDefault()} + onMouseDown={(e) => { + e.preventDefault(); + e.currentTarget.focus(); + }} />
{ -
- { - if (isLoading) { - return; - } - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault(); - onSubmit(e as unknown as React.FormEvent); - } - }} - placeholder="Message ChatGPT" - value={input} - rows={1} - style={{ height: 42 }} - minHeight={42} - maxHeight={200} - onChange={handleInputChange} - className="focus-visible:ring-0 pr-0 resize-none bg-transparent focus-within:outline-none sm:text-base border-none" - /> - +
+ {error && ( +
+

{error?.message}

+
+ )} +
+ { + if (isLoading) { + return; + } + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + onSubmit(e as unknown as React.FormEvent); + } + }} + placeholder="Message ChatGPT" + value={input} + rows={1} + style={{ height: 42 }} + minHeight={42} + maxHeight={200} + onChange={handleInputChange} + className="focus-visible:ring-0 pr-0 resize-none bg-transparent focus-within:outline-none sm:text-base border-none" + /> + +
diff --git a/apps/www/src/lib/maskKey.ts b/apps/www/src/lib/maskKey.ts new file mode 100644 index 00000000..80127fd2 --- /dev/null +++ b/apps/www/src/lib/maskKey.ts @@ -0,0 +1,5 @@ +export const getMaskedKey = (value: string): string => { + const charsToShow = 3; + const maskedLength = Math.max(0, value.length - charsToShow); + return `${value.substring(0, charsToShow)}${"*".repeat(maskedLength)}`; +}; diff --git a/apps/www/src/pages/api/chat.ts b/apps/www/src/pages/api/chat.ts index c6d3a9e6..b96e563e 100644 --- a/apps/www/src/pages/api/chat.ts +++ b/apps/www/src/pages/api/chat.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + import { OpenAIStream, StreamingTextResponse } from "ai"; import type { APIRoute } from "astro"; import OpenAI from "openai"; @@ -11,6 +13,13 @@ const chatRequestSchema = z.object({ model: z.string(), }); +const getApiKey = (apiKey: string, model: string) => { + if (model === "gpt-3.5-turbo" && apiKey.length === 0) { + return process.env.OPENAI_API_KEY; + } + return apiKey; +}; + export const POST: APIRoute = async ({ request }) => { const jsonBody = await request.json(); const result = chatRequestSchema.safeParse(jsonBody); @@ -23,15 +32,24 @@ export const POST: APIRoute = async ({ request }) => { ); } const { apiKey, messages, model } = result.data; - const openai = new OpenAI({ - apiKey, - }); - const completion = await openai.chat.completions.create({ - model, - messages: messages, - stream: true, - }); - const stream = OpenAIStream(completion); + try { + const openai = new OpenAI({ + apiKey: getApiKey(apiKey, model), + }); + const completion = await openai.chat.completions.create({ + model, + messages: messages, + stream: true, + }); + const stream = OpenAIStream(completion); - return new StreamingTextResponse(stream); + return new StreamingTextResponse(stream); + } catch (error: any) { + return new Response( + JSON.stringify({ + message: `${error.message}`, + }), + { status: 500 }, + ); + } };