add api key
This commit is contained in:
		
							parent
							
								
									396fe4ec6f
								
							
						
					
					
						commit
						e6449998ef
					
				|  | @ -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)} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | @ -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> | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -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) { | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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({ | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue