feat: add in prettier and format code for consistency (#168)

This commit is contained in:
Simon Holmes
2023-03-26 05:13:18 +00:00
committed by GitHub
parent b843f6e0e0
commit d6973b9ccc
72 changed files with 1140 additions and 4573 deletions
+6 -6
View File
@@ -1,9 +1,9 @@
import "@/styles/globals.css";
import { appWithTranslation } from "next-i18next";
import type { AppProps } from "next/app";
import { Inter } from "next/font/google";
import '@/styles/globals.css';
import { appWithTranslation } from 'next-i18next';
import type { AppProps } from 'next/app';
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ["latin"] });
const inter = Inter({ subsets: ['latin'] });
function App({ Component, pageProps }: AppProps<{}>) {
return (
@@ -13,4 +13,4 @@ function App({ Component, pageProps }: AppProps<{}>) {
);
}
export default appWithTranslation(App);
export default appWithTranslation(App);
+6 -7
View File
@@ -1,18 +1,17 @@
import { Html, Head, Main, NextScript, DocumentProps } from 'next/document'
import i18nextConfig from '../next-i18next.config'
import { Html, Head, Main, NextScript, DocumentProps } from 'next/document';
import i18nextConfig from '../next-i18next.config';
type Props = DocumentProps & {
// add custom document props
}
};
export default function Document(props: Props) {
const currentLocale =
props.__NEXT_DATA__.locale ??
i18nextConfig.i18n.defaultLocale
props.__NEXT_DATA__.locale ?? i18nextConfig.i18n.defaultLocale;
return (
<Html lang={currentLocale}>
<Head>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Chatbot UI"></meta>
</Head>
<body>
@@ -20,5 +19,5 @@ export default function Document(props: Props) {
<NextScript />
</body>
</Html>
)
);
}
+9 -9
View File
@@ -1,13 +1,13 @@
import { ChatBody, Message, OpenAIModelID } from "@/types";
import { DEFAULT_SYSTEM_PROMPT } from "@/utils/app/const";
import { OpenAIStream } from "@/utils/server";
import tiktokenModel from "@dqbd/tiktoken/encoders/cl100k_base.json";
import { init, Tiktoken } from "@dqbd/tiktoken/lite/init";
import { ChatBody, Message, OpenAIModelID } from '@/types';
import { DEFAULT_SYSTEM_PROMPT } from '@/utils/app/const';
import { OpenAIStream } from '@/utils/server';
import tiktokenModel from '@dqbd/tiktoken/encoders/cl100k_base.json';
import { init, Tiktoken } from '@dqbd/tiktoken/lite/init';
// @ts-expect-error
import wasm from "../../node_modules/@dqbd/tiktoken/lite/tiktoken_bg.wasm?module";
import wasm from '../../node_modules/@dqbd/tiktoken/lite/tiktoken_bg.wasm?module';
export const config = {
runtime: "edge",
runtime: 'edge',
};
const handler = async (req: Request): Promise<Response> => {
@@ -18,7 +18,7 @@ const handler = async (req: Request): Promise<Response> => {
const encoding = new Tiktoken(
tiktokenModel.bpe_ranks,
tiktokenModel.special_tokens,
tiktokenModel.pat_str
tiktokenModel.pat_str,
);
const tokenLimit = model.id === OpenAIModelID.GPT_4 ? 6000 : 3000;
@@ -51,7 +51,7 @@ const handler = async (req: Request): Promise<Response> => {
return new Response(stream);
} catch (error) {
console.error(error);
return new Response("Error", { status: 500 });
return new Response('Error', { status: 500 });
}
};
+18 -17
View File
@@ -1,8 +1,8 @@
import { OpenAIModel, OpenAIModelID, OpenAIModels } from "@/types";
import { OPENAI_API_HOST } from "@/utils/app/const";
import { OpenAIModel, OpenAIModelID, OpenAIModels } from '@/types';
import { OPENAI_API_HOST } from '@/utils/app/const';
export const config = {
runtime: "edge"
runtime: 'edge',
};
const handler = async (req: Request): Promise<Response> => {
@@ -13,22 +13,23 @@ const handler = async (req: Request): Promise<Response> => {
const response = await fetch(`${OPENAI_API_HOST}/v1/models`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${key ? key : process.env.OPENAI_API_KEY}`
}
'Content-Type': 'application/json',
Authorization: `Bearer ${key ? key : process.env.OPENAI_API_KEY}`,
},
});
if (response.status === 401) {
return new Response(
response.body,
{
status: 500,
headers: response.headers
}
);
return new Response(response.body, {
status: 500,
headers: response.headers,
});
} else if (response.status !== 200) {
console.error(`OpenAI API returned an error ${response.status}: ${await response.text()}`)
throw new Error("OpenAI API returned an error");
console.error(
`OpenAI API returned an error ${
response.status
}: ${await response.text()}`,
);
throw new Error('OpenAI API returned an error');
}
const json = await response.json();
@@ -39,7 +40,7 @@ const handler = async (req: Request): Promise<Response> => {
if (value === model.id) {
return {
id: model.id,
name: OpenAIModels[value].name
name: OpenAIModels[value].name,
};
}
}
@@ -49,7 +50,7 @@ const handler = async (req: Request): Promise<Response> => {
return new Response(JSON.stringify(models), { status: 200 });
} catch (error) {
console.error(error);
return new Response("Error", { status: 500 });
return new Response('Error', { status: 500 });
}
};
+158 -114
View File
@@ -1,35 +1,52 @@
import { Chat } from "@/components/Chat/Chat";
import { Navbar } from "@/components/Mobile/Navbar";
import { Sidebar } from "@/components/Sidebar/Sidebar";
import { ChatBody, ChatFolder, Conversation, ErrorMessage, KeyValuePair, Message, OpenAIModel, OpenAIModelID, OpenAIModels } from "@/types";
import { cleanConversationHistory, cleanSelectedConversation } from "@/utils/app/clean";
import { DEFAULT_SYSTEM_PROMPT } from "@/utils/app/const";
import { saveConversation, saveConversations, updateConversation } from "@/utils/app/conversation";
import { saveFolders } from "@/utils/app/folders";
import { exportData, importData } from "@/utils/app/importExport";
import { IconArrowBarLeft, IconArrowBarRight } from "@tabler/icons-react";
import { GetServerSideProps } from "next";
import Head from "next/head";
import { useEffect, useRef, useState } from "react";
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from "next-i18next";
import { Chat } from '@/components/Chat/Chat';
import { Navbar } from '@/components/Mobile/Navbar';
import { Sidebar } from '@/components/Sidebar/Sidebar';
import {
ChatBody,
ChatFolder,
Conversation,
ErrorMessage,
KeyValuePair,
Message,
OpenAIModel,
OpenAIModelID,
OpenAIModels,
} from '@/types';
import {
cleanConversationHistory,
cleanSelectedConversation,
} from '@/utils/app/clean';
import { DEFAULT_SYSTEM_PROMPT } from '@/utils/app/const';
import {
saveConversation,
saveConversations,
updateConversation,
} from '@/utils/app/conversation';
import { saveFolders } from '@/utils/app/folders';
import { exportData, importData } from '@/utils/app/importExport';
import { IconArrowBarLeft, IconArrowBarRight } from '@tabler/icons-react';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
import { useEffect, useRef, useState } from 'react';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next';
interface HomeProps {
serverSideApiKeyIsSet: boolean;
}
const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
const { t } = useTranslation('chat')
const { t } = useTranslation('chat');
const [folders, setFolders] = useState<ChatFolder[]>([]);
const [conversations, setConversations] = useState<Conversation[]>([]);
const [selectedConversation, setSelectedConversation] = useState<Conversation>();
const [selectedConversation, setSelectedConversation] =
useState<Conversation>();
const [loading, setLoading] = useState<boolean>(false);
const [models, setModels] = useState<OpenAIModel[]>([]);
const [lightMode, setLightMode] = useState<"dark" | "light">("dark");
const [lightMode, setLightMode] = useState<'dark' | 'light'>('dark');
const [messageIsStreaming, setMessageIsStreaming] = useState<boolean>(false);
const [showSidebar, setShowSidebar] = useState<boolean>(true);
const [apiKey, setApiKey] = useState<string>("");
const [apiKey, setApiKey] = useState<string>('');
const [messageError, setMessageError] = useState<boolean>(false);
const [modelError, setModelError] = useState<ErrorMessage | null>(null);
const [currentMessage, setCurrentMessage] = useState<Message>();
@@ -48,12 +65,12 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
updatedConversation = {
...selectedConversation,
messages: [...updatedMessages, message]
messages: [...updatedMessages, message],
};
} else {
updatedConversation = {
...selectedConversation,
messages: [...selectedConversation.messages, message]
messages: [...selectedConversation.messages, message],
};
}
@@ -66,17 +83,17 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
model: updatedConversation.model,
messages: updatedConversation.messages,
key: apiKey,
prompt: updatedConversation.prompt
prompt: updatedConversation.prompt,
};
const controller = new AbortController();
const response = await fetch("/api/chat", {
method: "POST",
const response = await fetch('/api/chat', {
method: 'POST',
headers: {
"Content-Type": "application/json"
'Content-Type': 'application/json',
},
signal: controller.signal,
body: JSON.stringify(chatBody)
body: JSON.stringify(chatBody),
});
if (!response.ok) {
@@ -98,11 +115,12 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
if (updatedConversation.messages.length === 1) {
const { content } = message;
const customName = content.length > 30 ? content.substring(0, 30) + "..." : content;
const customName =
content.length > 30 ? content.substring(0, 30) + '...' : content;
updatedConversation = {
...updatedConversation,
name: customName
name: customName,
};
}
@@ -112,7 +130,7 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
const decoder = new TextDecoder();
let done = false;
let isFirst = true;
let text = "";
let text = '';
while (!done) {
if (stopConversationRef.current === true) {
@@ -128,29 +146,34 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
if (isFirst) {
isFirst = false;
const updatedMessages: Message[] = [...updatedConversation.messages, { role: "assistant", content: chunkValue }];
const updatedMessages: Message[] = [
...updatedConversation.messages,
{ role: 'assistant', content: chunkValue },
];
updatedConversation = {
...updatedConversation,
messages: updatedMessages
messages: updatedMessages,
};
setSelectedConversation(updatedConversation);
} else {
const updatedMessages: Message[] = updatedConversation.messages.map((message, index) => {
if (index === updatedConversation.messages.length - 1) {
return {
...message,
content: text
};
}
const updatedMessages: Message[] = updatedConversation.messages.map(
(message, index) => {
if (index === updatedConversation.messages.length - 1) {
return {
...message,
content: text,
};
}
return message;
});
return message;
},
);
updatedConversation = {
...updatedConversation,
messages: updatedMessages
messages: updatedMessages,
};
setSelectedConversation(updatedConversation);
@@ -159,13 +182,15 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
saveConversation(updatedConversation);
const updatedConversations: Conversation[] = conversations.map((conversation) => {
if (conversation.id === selectedConversation.id) {
return updatedConversation;
}
const updatedConversations: Conversation[] = conversations.map(
(conversation) => {
if (conversation.id === selectedConversation.id) {
return updatedConversation;
}
return conversation;
});
return conversation;
},
);
if (updatedConversations.length === 0) {
updatedConversations.push(updatedConversation);
@@ -184,19 +209,21 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
title: t('Error fetching models.'),
code: null,
messageLines: [
t('Make sure your OpenAI API key is set in the bottom left of the sidebar.'),
t('If you completed this step, OpenAI may be experiencing issues.')
]
t(
'Make sure your OpenAI API key is set in the bottom left of the sidebar.',
),
t('If you completed this step, OpenAI may be experiencing issues.'),
],
} as ErrorMessage;
const response = await fetch("/api/models", {
method: "POST",
const response = await fetch('/api/models', {
method: 'POST',
headers: {
"Content-Type": "application/json"
'Content-Type': 'application/json',
},
body: JSON.stringify({
key
})
key,
}),
});
if (!response.ok) {
@@ -204,9 +231,9 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
const data = await response.json();
Object.assign(error, {
code: data.error?.code,
messageLines: [data.error?.message]
})
} catch (e) { }
messageLines: [data.error?.message],
});
} catch (e) {}
setModelError(error);
return;
}
@@ -222,21 +249,24 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
setModelError(null);
};
const handleLightMode = (mode: "dark" | "light") => {
const handleLightMode = (mode: 'dark' | 'light') => {
setLightMode(mode);
localStorage.setItem("theme", mode);
localStorage.setItem('theme', mode);
};
const handleApiKeyChange = (apiKey: string) => {
setApiKey(apiKey);
localStorage.setItem("apiKey", apiKey);
localStorage.setItem('apiKey', apiKey);
};
const handleExportData = () => {
exportData();
};
const handleImportConversations = (data: { conversations: Conversation[]; folders: ChatFolder[] }) => {
const handleImportConversations = (data: {
conversations: Conversation[];
folders: ChatFolder[];
}) => {
importData(data.conversations, data.folders);
setConversations(data.conversations);
setSelectedConversation(data.conversations[data.conversations.length - 1]);
@@ -253,7 +283,7 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
const newFolder: ChatFolder = {
id: lastFolder ? lastFolder.id + 1 : 1,
name
name,
};
const updatedFolders = [...folders, newFolder];
@@ -271,7 +301,7 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
if (c.folderId === folderId) {
return {
...c,
folderId: 0
folderId: 0,
};
}
@@ -286,7 +316,7 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
if (f.id === folderId) {
return {
...f,
name
name,
};
}
@@ -302,11 +332,13 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
const newConversation: Conversation = {
id: lastConversation ? lastConversation.id + 1 : 1,
name: `${t('Conversation')} ${lastConversation ? lastConversation.id + 1 : 1}`,
name: `${t('Conversation')} ${
lastConversation ? lastConversation.id + 1 : 1
}`,
messages: [],
model: OpenAIModels[OpenAIModelID.GPT_3_5],
prompt: DEFAULT_SYSTEM_PROMPT,
folderId: 0
folderId: 0,
};
const updatedConversations = [...conversations, newConversation];
@@ -321,33 +353,43 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
};
const handleDeleteConversation = (conversation: Conversation) => {
const updatedConversations = conversations.filter((c) => c.id !== conversation.id);
const updatedConversations = conversations.filter(
(c) => c.id !== conversation.id,
);
setConversations(updatedConversations);
saveConversations(updatedConversations);
if (updatedConversations.length > 0) {
setSelectedConversation(updatedConversations[updatedConversations.length - 1]);
setSelectedConversation(
updatedConversations[updatedConversations.length - 1],
);
saveConversation(updatedConversations[updatedConversations.length - 1]);
} else {
setSelectedConversation({
id: 1,
name: "New conversation",
name: 'New conversation',
messages: [],
model: OpenAIModels[OpenAIModelID.GPT_3_5],
prompt: DEFAULT_SYSTEM_PROMPT,
folderId: 0
folderId: 0,
});
localStorage.removeItem("selectedConversation");
localStorage.removeItem('selectedConversation');
}
};
const handleUpdateConversation = (conversation: Conversation, data: KeyValuePair) => {
const handleUpdateConversation = (
conversation: Conversation,
data: KeyValuePair,
) => {
const updatedConversation = {
...conversation,
[data.key]: data.value
[data.key]: data.value,
};
const { single, all } = updateConversation(updatedConversation, conversations);
const { single, all } = updateConversation(
updatedConversation,
conversations,
);
setSelectedConversation(single);
setConversations(all);
@@ -355,20 +397,20 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
const handleClearConversations = () => {
setConversations([]);
localStorage.removeItem("conversationHistory");
localStorage.removeItem('conversationHistory');
setSelectedConversation({
id: 1,
name: "New conversation",
name: 'New conversation',
messages: [],
model: OpenAIModels[OpenAIModelID.GPT_3_5],
prompt: DEFAULT_SYSTEM_PROMPT,
folderId: 0
folderId: 0,
});
localStorage.removeItem("selectedConversation");
localStorage.removeItem('selectedConversation');
setFolders([]);
localStorage.removeItem("folders");
localStorage.removeItem('folders');
};
const handleEditMessage = (message: Message, messageIndex: number) => {
@@ -383,10 +425,13 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
const updatedConversation = {
...selectedConversation,
messages: updatedMessages
messages: updatedMessages,
};
const { single, all } = updateConversation(updatedConversation, conversations);
const { single, all } = updateConversation(
updatedConversation,
conversations,
);
setSelectedConversation(single);
setConversations(all);
@@ -415,48 +460,54 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
}, [apiKey]);
useEffect(() => {
const theme = localStorage.getItem("theme");
const theme = localStorage.getItem('theme');
if (theme) {
setLightMode(theme as "dark" | "light");
setLightMode(theme as 'dark' | 'light');
}
const apiKey = localStorage.getItem("apiKey");
const apiKey = localStorage.getItem('apiKey');
if (apiKey) {
setApiKey(apiKey);
fetchModels(apiKey);
} else if (serverSideApiKeyIsSet) {
fetchModels("");
fetchModels('');
}
if (window.innerWidth < 640) {
setShowSidebar(false);
}
const folders = localStorage.getItem("folders");
const folders = localStorage.getItem('folders');
if (folders) {
setFolders(JSON.parse(folders));
}
const conversationHistory = localStorage.getItem("conversationHistory");
const conversationHistory = localStorage.getItem('conversationHistory');
if (conversationHistory) {
const parsedConversationHistory: Conversation[] = JSON.parse(conversationHistory);
const cleanedConversationHistory = cleanConversationHistory(parsedConversationHistory);
const parsedConversationHistory: Conversation[] =
JSON.parse(conversationHistory);
const cleanedConversationHistory = cleanConversationHistory(
parsedConversationHistory,
);
setConversations(cleanedConversationHistory);
}
const selectedConversation = localStorage.getItem("selectedConversation");
const selectedConversation = localStorage.getItem('selectedConversation');
if (selectedConversation) {
const parsedSelectedConversation: Conversation = JSON.parse(selectedConversation);
const cleanedSelectedConversation = cleanSelectedConversation(parsedSelectedConversation);
const parsedSelectedConversation: Conversation =
JSON.parse(selectedConversation);
const cleanedSelectedConversation = cleanSelectedConversation(
parsedSelectedConversation,
);
setSelectedConversation(cleanedSelectedConversation);
} else {
setSelectedConversation({
id: 1,
name: "New conversation",
name: 'New conversation',
messages: [],
model: OpenAIModels[OpenAIModelID.GPT_3_5],
prompt: DEFAULT_SYSTEM_PROMPT,
folderId: 0
folderId: 0,
});
}
}, [serverSideApiKeyIsSet]);
@@ -465,22 +516,15 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
<>
<Head>
<title>Chatbot UI</title>
<meta
name="description"
content="ChatGPT but better."
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<link
rel="icon"
href="/favicon.ico"
/>
<meta name="description" content="ChatGPT but better." />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
{selectedConversation && (
<main className={`flex flex-col h-screen w-screen text-white dark:text-white text-sm ${lightMode}`}>
<div className="sm:hidden w-full fixed top-0">
<main
className={`flex h-screen w-screen flex-col text-sm text-white dark:text-white ${lightMode}`}
>
<div className="fixed top-0 w-full sm:hidden">
<Navbar
selectedConversation={selectedConversation}
onNewConversation={handleNewConversation}
@@ -513,18 +557,18 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
/>
<IconArrowBarLeft
className="z-50 fixed top-5 left-[270px] sm:top-0.5 sm:left-[270px] sm:text-neutral-700 dark:text-white cursor-pointer hover:text-gray-400 dark:hover:text-gray-300 h-7 w-7 sm:h-8 sm:w-8"
className="fixed top-5 left-[270px] z-50 h-7 w-7 cursor-pointer hover:text-gray-400 dark:text-white dark:hover:text-gray-300 sm:top-0.5 sm:left-[270px] sm:h-8 sm:w-8 sm:text-neutral-700"
onClick={() => setShowSidebar(!showSidebar)}
/>
<div
onClick={() => setShowSidebar(!showSidebar)}
className="sm:hidden bg-black opacity-70 z-10 absolute top-0 left-0 h-full w-full"
className="absolute top-0 left-0 z-10 h-full w-full bg-black opacity-70 sm:hidden"
></div>
</div>
) : (
<IconArrowBarRight
className="fixed text-white z-50 top-2.5 left-4 sm:top-0.5 sm:left-4 sm:text-neutral-700 dark:text-white cursor-pointer hover:text-gray-400 dark:hover:text-gray-300 h-7 w-7 sm:h-8 sm:w-8"
className="fixed top-2.5 left-4 z-50 h-7 w-7 cursor-pointer text-white hover:text-gray-400 dark:text-white dark:hover:text-gray-300 sm:top-0.5 sm:left-4 sm:h-8 sm:w-8 sm:text-neutral-700"
onClick={() => setShowSidebar(!showSidebar)}
/>
)}
@@ -562,6 +606,6 @@ export const getServerSideProps: GetServerSideProps = async ({ locale }) => {
'sidebar',
'markdown',
])),
}
},
};
};