diff --git a/components/Chat/Chat.tsx b/components/Chat/Chat.tsx index c0f9cb2..1318322 100644 --- a/components/Chat/Chat.tsx +++ b/components/Chat/Chat.tsx @@ -1,4 +1,4 @@ -import { Message, OpenAIModel, OpenAIModelNames } from "@/types"; +import { Conversation, Message, OpenAIModel } from "@/types"; import { FC, useEffect, useRef } from "react"; import { ChatInput } from "./ChatInput"; import { ChatLoader } from "./ChatLoader"; @@ -6,16 +6,16 @@ import { ChatMessage } from "./ChatMessage"; import { ModelSelect } from "./ModelSelect"; interface Props { - model: OpenAIModel; - messages: Message[]; + conversation: Conversation; + models: OpenAIModel[]; messageIsStreaming: boolean; loading: boolean; lightMode: "light" | "dark"; onSend: (message: Message) => void; - onSelect: (model: OpenAIModel) => void; + onModelChange: (conversation: Conversation, model: OpenAIModel) => void; } -export const Chat: FC = ({ model, messages, messageIsStreaming, loading, lightMode, onSend, onSelect }) => { +export const Chat: FC = ({ conversation, models, messageIsStreaming, loading, lightMode, onSend, onModelChange }) => { const messagesEndRef = useRef(null); const scrollToBottom = () => { @@ -24,27 +24,28 @@ export const Chat: FC = ({ model, messages, messageIsStreaming, loading, useEffect(() => { scrollToBottom(); - }, [messages]); + }, [conversation.messages]); return (
- {messages.length === 0 ? ( + {conversation.messages.length === 0 ? ( <>
onModelChange(conversation, model)} />
-
Chatbot UI
+
{loading ? "Loading..." : "Chatbot UI"}
) : ( <> -
Model: {OpenAIModelNames[model]}
+
Model: {conversation.model.name}
- {messages.map((message, index) => ( + {conversation.messages.map((message, index) => ( void; + models: OpenAIModel[]; + onModelChange: (model: OpenAIModel) => void; } -export const ModelSelect: FC = ({ model, onSelect }) => { +export const ModelSelect: FC = ({ model, models, onModelChange }) => { return (
diff --git a/pages/api/chat.ts b/pages/api/chat.ts index 54fdac3..cf1fc01 100644 --- a/pages/api/chat.ts +++ b/pages/api/chat.ts @@ -1,5 +1,5 @@ import { Message, OpenAIModel } from "@/types"; -import { OpenAIStream } from "@/utils"; +import { OpenAIStream } from "@/utils/server"; export const config = { runtime: "edge" @@ -23,7 +23,7 @@ const handler = async (req: Request): Promise => { break; } charCount += message.content.length; - messagesToSend = [message, ...messagesToSend] + messagesToSend = [message, ...messagesToSend]; } const stream = await OpenAIStream(model, key, messagesToSend); diff --git a/pages/api/models.ts b/pages/api/models.ts new file mode 100644 index 0000000..433b7f7 --- /dev/null +++ b/pages/api/models.ts @@ -0,0 +1,46 @@ +import { OpenAIModel, OpenAIModelID, OpenAIModels } from "@/types"; + +export const config = { + runtime: "edge" +}; + +const handler = async (req: Request): Promise => { + try { + const { key } = (await req.json()) as { + key: string; + }; + + const response = await fetch("https://api.openai.com/v1/models", { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${key ? key : process.env.OPENAI_API_KEY}` + } + }); + + if (response.status !== 200) { + throw new Error("OpenAI API returned an error"); + } + + const json = await response.json(); + + const models: OpenAIModel[] = json.data + .map((model: any) => { + for (const [key, value] of Object.entries(OpenAIModelID)) { + if (value === model.id) { + return { + id: model.id, + name: OpenAIModels[value].name + }; + } + } + }) + .filter(Boolean); + + return new Response(JSON.stringify(models), { status: 200 }); + } catch (error) { + console.error(error); + return new Response("Error", { status: 500 }); + } +}; + +export default handler; diff --git a/pages/index.tsx b/pages/index.tsx index 5f05f67..0f1de47 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,7 +1,8 @@ import { Chat } from "@/components/Chat/Chat"; import { Navbar } from "@/components/Mobile/Navbar"; import { Sidebar } from "@/components/Sidebar/Sidebar"; -import { Conversation, Message, OpenAIModel } from "@/types"; +import { Conversation, Message, OpenAIModel, OpenAIModelID, OpenAIModels } from "@/types"; +import { cleanConversationHistory, cleanSelectedConversation } from "@/utils/app"; import { IconArrowBarLeft, IconArrowBarRight } from "@tabler/icons-react"; import Head from "next/head"; import { useEffect, useState } from "react"; @@ -10,7 +11,7 @@ export default function Home() { const [conversations, setConversations] = useState([]); const [selectedConversation, setSelectedConversation] = useState(); const [loading, setLoading] = useState(false); - const [model, setModel] = useState(OpenAIModel.GPT_3_5); + const [models, setModels] = useState([]); const [lightMode, setLightMode] = useState<"dark" | "light">("dark"); const [messageIsStreaming, setMessageIsStreaming] = useState(false); const [showSidebar, setShowSidebar] = useState(true); @@ -33,7 +34,7 @@ export default function Home() { "Content-Type": "application/json" }, body: JSON.stringify({ - model, + model: updatedConversation.model, messages: updatedConversation.messages, key: apiKey }) @@ -47,6 +48,8 @@ export default function Home() { const data = response.body; if (!data) { + setLoading(false); + setMessageIsStreaming(false); return; } @@ -144,13 +147,35 @@ export default function Home() { localStorage.setItem("selectedConversation", JSON.stringify(updatedConversation)); }; + const handleChangeModel = (conversation: Conversation, model: OpenAIModel) => { + const updatedConversation = { + ...conversation, + model + }; + + const updatedConversations = conversations.map((c) => { + if (c.id === updatedConversation.id) { + return updatedConversation; + } + + return c; + }); + + setConversations(updatedConversations); + localStorage.setItem("conversationHistory", JSON.stringify(updatedConversations)); + + setSelectedConversation(updatedConversation); + localStorage.setItem("selectedConversation", JSON.stringify(updatedConversation)); + }; + const handleNewConversation = () => { const lastConversation = conversations[conversations.length - 1]; const newConversation: Conversation = { id: lastConversation ? lastConversation.id + 1 : 1, name: `Conversation ${lastConversation ? lastConversation.id + 1 : 1}`, - messages: [] + messages: [], + model: OpenAIModels[OpenAIModelID.GPT_3_5] }; const updatedConversations = [...conversations, newConversation]; @@ -160,7 +185,6 @@ export default function Home() { setSelectedConversation(newConversation); localStorage.setItem("selectedConversation", JSON.stringify(newConversation)); - setModel(OpenAIModel.GPT_3_5); setLoading(false); }; @@ -181,7 +205,8 @@ export default function Home() { setSelectedConversation({ id: 1, name: "New conversation", - messages: [] + messages: [], + model: OpenAIModels[OpenAIModelID.GPT_3_5] }); localStorage.removeItem("selectedConversation"); } @@ -192,6 +217,27 @@ export default function Home() { localStorage.setItem("apiKey", apiKey); }; + const fetchModels = async () => { + setLoading(true); + + const response = await fetch("/api/models", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + key: apiKey + }) + }); + const data = await response.json(); + + if (data) { + setModels(data); + } + + setLoading(false); + }; + useEffect(() => { const theme = localStorage.getItem("theme"); if (theme) { @@ -208,21 +254,27 @@ export default function Home() { } const conversationHistory = localStorage.getItem("conversationHistory"); - if (conversationHistory) { - setConversations(JSON.parse(conversationHistory)); + const parsedConversationHistory: Conversation[] = JSON.parse(conversationHistory); + const cleanedConversationHistory = cleanConversationHistory(parsedConversationHistory); + setConversations(cleanedConversationHistory); } const selectedConversation = localStorage.getItem("selectedConversation"); if (selectedConversation) { - setSelectedConversation(JSON.parse(selectedConversation)); + const parsedSelectedConversation: Conversation = JSON.parse(selectedConversation); + const cleanedSelectedConversation = cleanSelectedConversation(parsedSelectedConversation); + setSelectedConversation(cleanedSelectedConversation); } else { setSelectedConversation({ id: 1, name: "New conversation", - messages: [] + messages: [], + model: OpenAIModels[OpenAIModelID.GPT_3_5] }); } + + fetchModels(); }, []); return ( @@ -242,7 +294,6 @@ export default function Home() { href="/favicon.ico" /> - {selectedConversation && (
@@ -283,13 +334,13 @@ export default function Home() { )}
diff --git a/types/index.ts b/types/index.ts index c6923d0..05fdfe2 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,13 +1,22 @@ -export enum OpenAIModel { - GPT_3_5 = "gpt-3.5-turbo", - GPT_3_5_LEGACY = "gpt-3.5-turbo-0301" - // GPT_4 = "gpt-4" +export interface OpenAIModel { + id: string; + name: string; } -export const OpenAIModelNames: Record = { - [OpenAIModel.GPT_3_5]: "Default (GPT-3.5)", - [OpenAIModel.GPT_3_5_LEGACY]: "Legacy (GPT-3.5)" - // [OpenAIModel.GPT_4]: "GPT-4" +export enum OpenAIModelID { + GPT_3_5 = "gpt-3.5-turbo", + GPT_4 = "gpt-4" +} + +export const OpenAIModels: Record = { + [OpenAIModelID.GPT_3_5]: { + id: OpenAIModelID.GPT_3_5, + name: "Default (GPT-3.5)" + }, + [OpenAIModelID.GPT_4]: { + id: OpenAIModelID.GPT_4, + name: "GPT-4" + } }; export interface Message { @@ -21,4 +30,13 @@ export interface Conversation { id: number; name: string; messages: Message[]; + model: OpenAIModel; +} + +// keep track of local storage schema +export interface LocalStorage { + apiKey: string; + conversationHistory: Conversation[]; + selectedConversation: Conversation; + theme: "light" | "dark"; } diff --git a/utils/app/index.ts b/utils/app/index.ts new file mode 100644 index 0000000..30a53fb --- /dev/null +++ b/utils/app/index.ts @@ -0,0 +1,33 @@ +import { Conversation, OpenAIModelID, OpenAIModels } from "@/types"; + +export const cleanConversationHistory = (history: Conversation[]) => { + // added model for each conversation (3/20/23) + + if (history.length === 0) { + return history; + } else { + return history.map((conversation) => { + if (conversation.model) { + return conversation; + } else { + return { + ...conversation, + model: OpenAIModels[OpenAIModelID.GPT_3_5] + }; + } + }); + } +}; + +export const cleanSelectedConversation = (conversation: Conversation) => { + // added model for each conversation (3/20/23) + + if (conversation.model) { + return conversation; + } else { + return { + ...conversation, + model: OpenAIModels[OpenAIModelID.GPT_3_5] + }; + } +}; diff --git a/utils/index.ts b/utils/server/index.ts similarity index 98% rename from utils/index.ts rename to utils/server/index.ts index ba50769..8ab5766 100644 --- a/utils/index.ts +++ b/utils/server/index.ts @@ -12,7 +12,7 @@ export const OpenAIStream = async (model: OpenAIModel, key: string, messages: Me }, method: "POST", body: JSON.stringify({ - model, + model: model.id, messages: [ { role: "system",