diff --git a/components/Sidebar/Key.tsx b/components/Sidebar/Key.tsx new file mode 100644 index 0000000..7a197bf --- /dev/null +++ b/components/Sidebar/Key.tsx @@ -0,0 +1,66 @@ +import { IconCheck, IconKey, IconX } from "@tabler/icons-react"; +import { FC, KeyboardEvent, useState } from "react"; +import { SidebarButton } from "./SidebarButton"; + +interface Props { + apiKey: string; + onApiKeyChange: (apiKey: string) => void; +} + +export const Key: FC = ({ apiKey, onApiKeyChange }) => { + const [isChanging, setIsChanging] = useState(false); + const [newKey, setNewKey] = useState(apiKey); + + const handleEnterDown = (e: KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + handleUpdateKey(newKey); + } + }; + + const handleUpdateKey = (newKey: string) => { + onApiKeyChange(newKey); + setIsChanging(false); + }; + + return isChanging ? ( +
+ + + setNewKey(e.target.value)} + onKeyDown={handleEnterDown} + /> + +
+ { + e.stopPropagation(); + handleUpdateKey(newKey); + }} + /> + + { + e.stopPropagation(); + setIsChanging(false); + setNewKey(apiKey); + }} + /> +
+
+ ) : ( + } + onClick={() => setIsChanging(true)} + /> + ); +}; diff --git a/components/Sidebar/Sidebar.tsx b/components/Sidebar/Sidebar.tsx index 0ad5436..dac2f5b 100644 --- a/components/Sidebar/Sidebar.tsx +++ b/components/Sidebar/Sidebar.tsx @@ -9,15 +9,17 @@ interface Props { conversations: Conversation[]; lightMode: "light" | "dark"; selectedConversation: Conversation; + apiKey: string; onNewConversation: () => void; onToggleLightMode: (mode: "light" | "dark") => void; onSelectConversation: (conversation: Conversation) => void; onDeleteConversation: (conversation: Conversation) => void; onToggleSidebar: () => void; onRenameConversation: (conversation: Conversation, name: string) => void; + onApiKeyChange: (apiKey: string) => void; } -export const Sidebar: FC = ({ loading, conversations, lightMode, selectedConversation, onNewConversation, onToggleLightMode, onSelectConversation, onDeleteConversation, onToggleSidebar, onRenameConversation }) => { +export const Sidebar: FC = ({ loading, conversations, lightMode, selectedConversation, apiKey, onNewConversation, onToggleLightMode, onSelectConversation, onDeleteConversation, onToggleSidebar, onRenameConversation, onApiKeyChange }) => { return (
@@ -52,7 +54,9 @@ export const Sidebar: FC = ({ loading, conversations, lightMode, selected
); diff --git a/components/Sidebar/SidebarSettings.tsx b/components/Sidebar/SidebarSettings.tsx index 38c42b3..c6e383f 100644 --- a/components/Sidebar/SidebarSettings.tsx +++ b/components/Sidebar/SidebarSettings.tsx @@ -1,20 +1,27 @@ -import { IconMoon, IconSun, IconTrash } from "@tabler/icons-react"; +import { IconMoon, IconSun } from "@tabler/icons-react"; import { FC } from "react"; +import { Key } from "./Key"; import { SidebarButton } from "./SidebarButton"; interface Props { lightMode: "light" | "dark"; + apiKey: string; onToggleLightMode: (mode: "light" | "dark") => void; + onApiKeyChange: (apiKey: string) => void; } -export const SidebarSettings: FC = ({ lightMode, onToggleLightMode }) => { +export const SidebarSettings: FC = ({ lightMode, apiKey, onToggleLightMode, onApiKeyChange }) => { return ( -
+
: } onClick={() => onToggleLightMode(lightMode === "light" ? "dark" : "light")} /> +
); }; diff --git a/pages/api/chat.ts b/pages/api/chat.ts index 987062f..a241c81 100644 --- a/pages/api/chat.ts +++ b/pages/api/chat.ts @@ -7,9 +7,10 @@ export const config = { const handler = async (req: Request): Promise => { try { - const { model, messages } = (await req.json()) as { + const { model, messages, key } = (await req.json()) as { model: OpenAIModel; messages: Message[]; + key: string; }; const charLimit = 12000; @@ -25,7 +26,7 @@ const handler = async (req: Request): Promise => { messagesToSend.push(message); } - const stream = await OpenAIStream(model, messagesToSend); + const stream = await OpenAIStream(model, key, messagesToSend); return new Response(stream); } catch (error) { diff --git a/pages/index.tsx b/pages/index.tsx index 79044c5..080208e 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -13,6 +13,7 @@ export default function Home() { const [lightMode, setLightMode] = useState<"dark" | "light">("dark"); const [messageIsStreaming, setMessageIsStreaming] = useState(false); const [showSidebar, setShowSidebar] = useState(true); + const [apiKey, setApiKey] = useState(""); const handleSend = async (message: Message) => { if (selectedConversation) { @@ -32,7 +33,8 @@ export default function Home() { }, body: JSON.stringify({ model, - messages: updatedConversation.messages + messages: updatedConversation.messages, + key: apiKey }) }); @@ -184,12 +186,22 @@ export default function Home() { } }; + const handleApiKeyChange = (apiKey: string) => { + setApiKey(apiKey); + localStorage.setItem("apiKey", apiKey); + }; + useEffect(() => { const theme = localStorage.getItem("theme"); if (theme) { setLightMode(theme as "dark" | "light"); } + const apiKey = localStorage.getItem("apiKey"); + if (apiKey) { + setApiKey(apiKey); + } + const conversationHistory = localStorage.getItem("conversationHistory"); if (conversationHistory) { @@ -234,12 +246,14 @@ export default function Home() { conversations={conversations} lightMode={lightMode} selectedConversation={selectedConversation} + apiKey={apiKey} onToggleLightMode={handleLightMode} onNewConversation={handleNewConversation} onSelectConversation={handleSelectConversation} onDeleteConversation={handleDeleteConversation} onToggleSidebar={() => setShowSidebar(!showSidebar)} onRenameConversation={handleRenameConversation} + onApiKeyChange={handleApiKeyChange} /> ) : ( { +export const OpenAIStream = async (model: OpenAIModel, key: string, messages: Message[]) => { const encoder = new TextEncoder(); const decoder = new TextDecoder(); const res = await fetch("https://api.openai.com/v1/chat/completions", { headers: { "Content-Type": "application/json", - Authorization: `Bearer ${process.env.OPENAI_API_KEY}` + Authorization: `Bearer ${key ? key : process.env.OPENAI_API_KEY}` }, method: "POST", body: JSON.stringify({