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