add api key

This commit is contained in:
Mckay Wrigley 2023-03-18 22:19:19 -06:00
parent 396fe4ec6f
commit e6449998ef
6 changed files with 101 additions and 9 deletions

View File

@ -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<Props> = ({ apiKey, onApiKeyChange }) => {
const [isChanging, setIsChanging] = useState(false);
const [newKey, setNewKey] = useState(apiKey);
const handleEnterDown = (e: KeyboardEvent<HTMLDivElement>) => {
if (e.key === "Enter") {
e.preventDefault();
handleUpdateKey(newKey);
}
};
const handleUpdateKey = (newKey: string) => {
onApiKeyChange(newKey);
setIsChanging(false);
};
return isChanging ? (
<div className="flex hover:bg-[#343541] py-2 px-2 rounded-md cursor-pointer w-full items-center">
<IconKey size={16} />
<input
className="ml-2 flex-1 bg-transparent border-b border-neutral-400 focus:border-neutral-100 text-left overflow-hidden overflow-ellipsis pr-1 outline-none text-white"
type="password"
value={newKey}
onChange={(e) => setNewKey(e.target.value)}
onKeyDown={handleEnterDown}
/>
<div className="flex w-[40px]">
<IconCheck
className="ml-auto min-w-[20px] text-neutral-400 hover:text-neutral-100"
size={18}
onClick={(e) => {
e.stopPropagation();
handleUpdateKey(newKey);
}}
/>
<IconX
className="ml-auto min-w-[20px] text-neutral-400 hover:text-neutral-100"
size={18}
onClick={(e) => {
e.stopPropagation();
setIsChanging(false);
setNewKey(apiKey);
}}
/>
</div>
</div>
) : (
<SidebarButton
text="OpenAI API Key"
icon={<IconKey size={16} />}
onClick={() => setIsChanging(true)}
/>
);
};

View File

@ -9,15 +9,17 @@ interface Props {
conversations: Conversation[]; conversations: Conversation[];
lightMode: "light" | "dark"; lightMode: "light" | "dark";
selectedConversation: Conversation; selectedConversation: Conversation;
apiKey: string;
onNewConversation: () => void; onNewConversation: () => void;
onToggleLightMode: (mode: "light" | "dark") => void; onToggleLightMode: (mode: "light" | "dark") => void;
onSelectConversation: (conversation: Conversation) => void; onSelectConversation: (conversation: Conversation) => void;
onDeleteConversation: (conversation: Conversation) => void; onDeleteConversation: (conversation: Conversation) => void;
onToggleSidebar: () => void; onToggleSidebar: () => void;
onRenameConversation: (conversation: Conversation, name: string) => void; onRenameConversation: (conversation: Conversation, name: string) => void;
onApiKeyChange: (apiKey: string) => void;
} }
export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selectedConversation, onNewConversation, onToggleLightMode, onSelectConversation, onDeleteConversation, onToggleSidebar, onRenameConversation }) => { export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selectedConversation, apiKey, onNewConversation, onToggleLightMode, onSelectConversation, onDeleteConversation, onToggleSidebar, onRenameConversation, onApiKeyChange }) => {
return ( return (
<div className="flex flex-col bg-[#202123] min-w-[260px] max-w-[260px]"> <div className="flex flex-col bg-[#202123] min-w-[260px] max-w-[260px]">
<div className="flex items-center h-[60px] pl-2"> <div className="flex items-center h-[60px] pl-2">
@ -52,7 +54,9 @@ export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selected
<SidebarSettings <SidebarSettings
lightMode={lightMode} lightMode={lightMode}
apiKey={apiKey}
onToggleLightMode={onToggleLightMode} onToggleLightMode={onToggleLightMode}
onApiKeyChange={onApiKeyChange}
/> />
</div> </div>
); );

View File

@ -1,20 +1,27 @@
import { IconMoon, IconSun, IconTrash } from "@tabler/icons-react"; import { IconMoon, IconSun } from "@tabler/icons-react";
import { FC } from "react"; import { FC } from "react";
import { Key } from "./Key";
import { SidebarButton } from "./SidebarButton"; import { SidebarButton } from "./SidebarButton";
interface Props { interface Props {
lightMode: "light" | "dark"; lightMode: "light" | "dark";
apiKey: string;
onToggleLightMode: (mode: "light" | "dark") => void; onToggleLightMode: (mode: "light" | "dark") => void;
onApiKeyChange: (apiKey: string) => void;
} }
export const SidebarSettings: FC<Props> = ({ lightMode, onToggleLightMode }) => { export const SidebarSettings: FC<Props> = ({ lightMode, apiKey, onToggleLightMode, onApiKeyChange }) => {
return ( return (
<div className="flex flex-col items-center border-t border-neutral-500 p-2 text-sm"> <div className="flex flex-col items-center border-t border-neutral-500 px-2 py-4 text-sm space-y-4">
<SidebarButton <SidebarButton
text={lightMode === "light" ? "Dark mode" : "Light mode"} text={lightMode === "light" ? "Dark mode" : "Light mode"}
icon={lightMode === "light" ? <IconMoon size={16} /> : <IconSun size={16} />} icon={lightMode === "light" ? <IconMoon size={16} /> : <IconSun size={16} />}
onClick={() => onToggleLightMode(lightMode === "light" ? "dark" : "light")} onClick={() => onToggleLightMode(lightMode === "light" ? "dark" : "light")}
/> />
<Key
apiKey={apiKey}
onApiKeyChange={onApiKeyChange}
/>
</div> </div>
); );
}; };

View File

@ -7,9 +7,10 @@ export const config = {
const handler = async (req: Request): Promise<Response> => { const handler = async (req: Request): Promise<Response> => {
try { try {
const { model, messages } = (await req.json()) as { const { model, messages, key } = (await req.json()) as {
model: OpenAIModel; model: OpenAIModel;
messages: Message[]; messages: Message[];
key: string;
}; };
const charLimit = 12000; const charLimit = 12000;
@ -25,7 +26,7 @@ const handler = async (req: Request): Promise<Response> => {
messagesToSend.push(message); messagesToSend.push(message);
} }
const stream = await OpenAIStream(model, messagesToSend); const stream = await OpenAIStream(model, key, messagesToSend);
return new Response(stream); return new Response(stream);
} catch (error) { } catch (error) {

View File

@ -13,6 +13,7 @@ export default function Home() {
const [lightMode, setLightMode] = useState<"dark" | "light">("dark"); const [lightMode, setLightMode] = useState<"dark" | "light">("dark");
const [messageIsStreaming, setMessageIsStreaming] = useState<boolean>(false); const [messageIsStreaming, setMessageIsStreaming] = useState<boolean>(false);
const [showSidebar, setShowSidebar] = useState<boolean>(true); const [showSidebar, setShowSidebar] = useState<boolean>(true);
const [apiKey, setApiKey] = useState<string>("");
const handleSend = async (message: Message) => { const handleSend = async (message: Message) => {
if (selectedConversation) { if (selectedConversation) {
@ -32,7 +33,8 @@ export default function Home() {
}, },
body: JSON.stringify({ body: JSON.stringify({
model, 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(() => { useEffect(() => {
const theme = localStorage.getItem("theme"); const theme = localStorage.getItem("theme");
if (theme) { if (theme) {
setLightMode(theme as "dark" | "light"); setLightMode(theme as "dark" | "light");
} }
const apiKey = localStorage.getItem("apiKey");
if (apiKey) {
setApiKey(apiKey);
}
const conversationHistory = localStorage.getItem("conversationHistory"); const conversationHistory = localStorage.getItem("conversationHistory");
if (conversationHistory) { if (conversationHistory) {
@ -234,12 +246,14 @@ export default function Home() {
conversations={conversations} conversations={conversations}
lightMode={lightMode} lightMode={lightMode}
selectedConversation={selectedConversation} selectedConversation={selectedConversation}
apiKey={apiKey}
onToggleLightMode={handleLightMode} onToggleLightMode={handleLightMode}
onNewConversation={handleNewConversation} onNewConversation={handleNewConversation}
onSelectConversation={handleSelectConversation} onSelectConversation={handleSelectConversation}
onDeleteConversation={handleDeleteConversation} onDeleteConversation={handleDeleteConversation}
onToggleSidebar={() => setShowSidebar(!showSidebar)} onToggleSidebar={() => setShowSidebar(!showSidebar)}
onRenameConversation={handleRenameConversation} onRenameConversation={handleRenameConversation}
onApiKeyChange={handleApiKeyChange}
/> />
) : ( ) : (
<IconArrowBarRight <IconArrowBarRight

View File

@ -1,14 +1,14 @@
import { Message, OpenAIModel } from "@/types"; import { Message, OpenAIModel } from "@/types";
import { createParser, ParsedEvent, ReconnectInterval } from "eventsource-parser"; import { createParser, ParsedEvent, ReconnectInterval } from "eventsource-parser";
export const OpenAIStream = async (model: OpenAIModel, messages: Message[]) => { export const OpenAIStream = async (model: OpenAIModel, key: string, messages: Message[]) => {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const decoder = new TextDecoder(); const decoder = new TextDecoder();
const res = await fetch("https://api.openai.com/v1/chat/completions", { const res = await fetch("https://api.openai.com/v1/chat/completions", {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}` Authorization: `Bearer ${key ? key : process.env.OPENAI_API_KEY}`
}, },
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({