chore: some small improvements (#223)

* chore: stylize error message div
chore: correct styles for sidebar btn
chore: add spinner and replace header "Please wait" on spinner
chore: correct Russian translate
chore: hide clear conversation btn if not conversations
chore: stylize "Need OpenAI key" div

* chore: corrent Russian translate
This commit is contained in:
Danil Shishkevich 2023-03-27 20:43:01 +07:00 committed by GitHub
parent d8e3844fb9
commit 3ca503a3f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 27 deletions

View File

@ -6,9 +6,10 @@ import {
OpenAIModel, OpenAIModel,
} from '@/types'; } from '@/types';
import { throttle } from '@/utils'; import { throttle } from '@/utils';
import { IconClearAll, IconSettings } from '@tabler/icons-react'; import { IconClearAll, IconKey, IconSettings } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { FC, memo, MutableRefObject, useEffect, useRef, useState } from 'react'; import { FC, memo, MutableRefObject, useEffect, useRef, useState } from 'react';
import { Spinner } from '../Global/Spinner';
import { ChatInput } from './ChatInput'; import { ChatInput } from './ChatInput';
import { ChatLoader } from './ChatLoader'; import { ChatLoader } from './ChatLoader';
import { ChatMessage } from './ChatMessage'; import { ChatMessage } from './ChatMessage';
@ -110,16 +111,22 @@ export const Chat: FC<Props> = memo(
<div className="overflow-none relative flex-1 bg-white dark:bg-[#343541]"> <div className="overflow-none relative flex-1 bg-white dark:bg-[#343541]">
{!(apiKey || serverSideApiKeyIsSet) ? ( {!(apiKey || serverSideApiKeyIsSet) ? (
<div className="mx-auto flex h-full w-[300px] flex-col justify-center space-y-6 sm:w-[500px]"> <div className="mx-auto flex h-full w-[300px] flex-col justify-center space-y-6 sm:w-[500px]">
<div className="mx-auto mb-5">
<IconKey size={36} />
</div>
<div className="text-center text-2xl font-semibold text-gray-800 dark:text-gray-100"> <div className="text-center text-2xl font-semibold text-gray-800 dark:text-gray-100">
{t('OpenAI API Key Required')} {t('OpenAI API Key Required')}
</div> </div>
<div className="text-center text-gray-500 dark:text-gray-400"> <div className="text-center text-gray-500 dark:text-gray-400">
<div className="mb-2">
{t( {t(
'Please set your OpenAI API key in the bottom left of the sidebar.', 'Please set your OpenAI API key in the bottom left of the sidebar.',
)} )}
</div> </div>
<div className="text-center text-gray-500 dark:text-gray-400"> <div>
{t("If you don't have an OpenAI API key, you can get one here: ")} {t(
"If you don't have an OpenAI API key, you can get one here: ",
)}
<a <a
href="https://platform.openai.com/account/api-keys" href="https://platform.openai.com/account/api-keys"
target="_blank" target="_blank"
@ -130,6 +137,7 @@ export const Chat: FC<Props> = memo(
</a> </a>
</div> </div>
</div> </div>
</div>
) : modelError ? ( ) : modelError ? (
<ErrorMessageDiv error={modelError} /> <ErrorMessageDiv error={modelError} />
) : ( ) : (
@ -142,11 +150,17 @@ export const Chat: FC<Props> = memo(
<> <>
<div className="mx-auto flex w-[350px] flex-col space-y-10 pt-12 sm:w-[600px]"> <div className="mx-auto flex w-[350px] flex-col space-y-10 pt-12 sm:w-[600px]">
<div className="text-center text-3xl font-semibold text-gray-800 dark:text-gray-100"> <div className="text-center text-3xl font-semibold text-gray-800 dark:text-gray-100">
{models.length === 0 ? t('Loading...') : 'Chatbot UI'} {models.length === 0 ? (
<div>
<Spinner size="16px" className="mx-auto" />
</div>
) : (
'Chatbot UI'
)}
</div> </div>
{models.length > 0 && ( {models.length > 0 && (
<div className="flex h-full flex-col space-y-4 rounded border border-neutral-200 p-4 dark:border-neutral-600"> <div className="flex h-full flex-col space-y-4 rounded-lg border border-neutral-200 p-4 dark:border-neutral-600">
<ModelSelect <ModelSelect
model={conversation.model} model={conversation.model}
models={models} models={models}

View File

@ -1,4 +1,5 @@
import { ErrorMessage } from '@/types'; import { ErrorMessage } from '@/types';
import { IconCircleX } from '@tabler/icons-react';
import { FC } from 'react'; import { FC } from 'react';
interface Props { interface Props {
@ -7,16 +8,20 @@ interface Props {
export const ErrorMessageDiv: FC<Props> = ({ error }) => { export const ErrorMessageDiv: FC<Props> = ({ error }) => {
return ( return (
<div className="mx-auto flex h-full w-[300px] flex-col justify-center space-y-6 sm:w-[500px]"> <div className="mx-6 flex h-full flex-col items-center justify-center text-red-500">
<div className="text-center text-red-500"> <div className="mb-5">
{error.title} {error.code ? <i>({error.code}) </i> : ''} <IconCircleX size={36} />
</div> </div>
<div className="mb-3 text-2xl font-medium">{error.title}</div>
{error.messageLines.map((line, index) => ( {error.messageLines.map((line, index) => (
<div key={index} className="text-center text-red-500"> <div key={index} className="text-center">
{' '} {' '}
{line}{' '} {line}{' '}
</div> </div>
))} ))}
<div className="text-xs dark:text-red-400 opacity-50 mt-4">
{error.code ? <i>Code: {error.code}</i> : ''}
</div>
</div> </div>
); );
}; };

View File

@ -0,0 +1,32 @@
import { FC } from 'react';
interface Props {
size?: string;
className?: string;
}
export const Spinner: FC<Props> = ({ size = '1em', className="" }) => {
return (
<svg
stroke="currentColor"
fill="none"
strokeWidth="2"
viewBox="0 0 24 24"
strokeLinecap="round"
strokeLinejoin="round"
className={`animate-spin ${className}`}
height={size}
width={size}
xmlns="http://www.w3.org/2000/svg"
>
<line x1="12" y1="2" x2="12" y2="6"></line>
<line x1="12" y1="18" x2="12" y2="22"></line>
<line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line>
<line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line>
<line x1="2" y1="12" x2="6" y2="12"></line>
<line x1="18" y1="12" x2="22" y2="12"></line>
<line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line>
<line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line>
</svg>
);
};

View File

@ -18,7 +18,7 @@ export const ClearConversations: FC<Props> = ({ onClearConversations }) => {
}; };
return isConfirming ? ( return isConfirming ? (
<div className="flex w-full cursor-pointer items-center rounded-md py-3 px-3 hover:bg-[#343541]"> <div className="flex w-full cursor-pointer items-center rounded-lg py-3 px-3 hover:bg-gray-500/10">
<IconTrash size={18} /> <IconTrash size={18} />
<div className="ml-3 flex-1 text-left text-[12.5px] leading-3 text-white"> <div className="ml-3 flex-1 text-left text-[12.5px] leading-3 text-white">

View File

@ -30,7 +30,7 @@ export const Key: FC<Props> = ({ apiKey, onApiKeyChange }) => {
<IconKey size={18} /> <IconKey size={18} />
<input <input
className="ml-2 h-[20px] flex-1 overflow-hidden overflow-ellipsis border-b border-neutral-400 bg-transparent pr-1 text-left text-white outline-none focus:border-neutral-100" className="ml-2 h-[20px] flex-1 overflow-hidden overflow-ellipsis border-b border-neutral-400 bg-transparent pr-1 text-[12.5px] leading-3 text-left text-white outline-none focus:border-neutral-100"
type="password" type="password"
value={newKey} value={newKey}
onChange={(e) => setNewKey(e.target.value)} onChange={(e) => setNewKey(e.target.value)}

View File

@ -202,6 +202,7 @@ export const Sidebar: FC<Props> = ({
<SidebarSettings <SidebarSettings
lightMode={lightMode} lightMode={lightMode}
apiKey={apiKey} apiKey={apiKey}
conversationsCount={conversations.length}
onToggleLightMode={onToggleLightMode} onToggleLightMode={onToggleLightMode}
onApiKeyChange={onApiKeyChange} onApiKeyChange={onApiKeyChange}
onClearConversations={onClearConversations} onClearConversations={onClearConversations}

View File

@ -10,6 +10,7 @@ import { SidebarButton } from './SidebarButton';
interface Props { interface Props {
lightMode: 'light' | 'dark'; lightMode: 'light' | 'dark';
apiKey: string; apiKey: string;
conversationsCount: number;
onToggleLightMode: (mode: 'light' | 'dark') => void; onToggleLightMode: (mode: 'light' | 'dark') => void;
onApiKeyChange: (apiKey: string) => void; onApiKeyChange: (apiKey: string) => void;
onClearConversations: () => void; onClearConversations: () => void;
@ -23,6 +24,7 @@ interface Props {
export const SidebarSettings: FC<Props> = ({ export const SidebarSettings: FC<Props> = ({
lightMode, lightMode,
apiKey, apiKey,
conversationsCount,
onToggleLightMode, onToggleLightMode,
onApiKeyChange, onApiKeyChange,
onClearConversations, onClearConversations,
@ -32,7 +34,9 @@ export const SidebarSettings: FC<Props> = ({
const { t } = useTranslation('sidebar'); const { t } = useTranslation('sidebar');
return ( return (
<div className="flex flex-col items-center space-y-1 border-t border-white/20 pt-1 text-sm"> <div className="flex flex-col items-center space-y-1 border-t border-white/20 pt-1 text-sm">
{conversationsCount > 0 ? (
<ClearConversations onClearConversations={onClearConversations} /> <ClearConversations onClearConversations={onClearConversations} />
) : null}
<Import onImport={onImportConversations} /> <Import onImport={onImportConversations} />

View File

@ -6,7 +6,7 @@
"System Prompt": "Системное сообщение", "System Prompt": "Системное сообщение",
"You are ChatGPT, a large language model trained by OpenAI. Follow the user's instructions carefully. Respond using markdown.": "Вы ChatGPT, большая языковая модель, созданная компанией OpenAI. Следуйте инструкциям пользователя. Отвечайте на сообщения, используя Markdown", "You are ChatGPT, a large language model trained by OpenAI. Follow the user's instructions carefully. Respond using markdown.": "Вы ChatGPT, большая языковая модель, созданная компанией OpenAI. Следуйте инструкциям пользователя. Отвечайте на сообщения, используя Markdown",
"Enter a prompt": "Введите сообщение", "Enter a prompt": "Введите сообщение",
"Regenerate response": "Сгенерировать сообщение снова", "Regenerate response": "Перегенерировать сообщение",
"Sorry, there was an error.": "Просим прощения, произошла ошибка", "Sorry, there was an error.": "Просим прощения, произошла ошибка",
"Model": "Модель", "Model": "Модель",
"Conversation": "Чат", "Conversation": "Чат",
@ -17,11 +17,11 @@
"AI": "Бот", "AI": "Бот",
"You": "Вы", "You": "Вы",
"Cancel": "Отмена", "Cancel": "Отмена",
"Save & Submit": "Сохранить и отправить", "Save & Submit": "Отредактировать",
"Make sure your OpenAI API key is set in the bottom left of the sidebar.": "Убедитесь, что вы ввели API-ключ OpenAI.", "Make sure your OpenAI API key is set in the bottom left of the sidebar.": "Убедитесь, что вы ввели API-ключ OpenAI.",
"If you completed this step, OpenAI may be experiencing issues.": "Если вы выполнили этот шаг, то возможно OpenAI может испытывать проблемы", "If you completed this step, OpenAI may be experiencing issues.": "Если вы выполнили этот шаг, то возможно OpenAI может испытывать проблемы",
"Message limit is {{maxLength}} characters. You have entered {{valueLength}} characters.": "Лимит сообщения: {{maxLength}} символов. Вы ввели {{valueLength}} символов.", "Message limit is {{maxLength}} characters. You have entered {{valueLength}} characters.": "Лимит сообщения: {{maxLength}} символов. Вы ввели {{valueLength}} символов.",
"Please enter a message": "Пожалуйста введите сообщение", "Please enter a message": "Пожалуйста введите сообщение",
"Chatbot UI is an advanced chatbot kit for OpenAI's chat models aiming to mimic ChatGPT's interface and functionality.": "Chatbot UI - продвинутый интерфейс чатбота для чат-моделей OpenAI, которое имитирует интерфейс ChatGPT", "Chatbot UI is an advanced chatbot kit for OpenAI's chat models aiming to mimic ChatGPT's interface and functionality.": "Chatbot UI - продвинутый интерфейс чатбота для чат-моделей OpenAI, имитирующий интерфейс ChatGPT",
"Are you sure you want to clear all messages?": "Вы уверены, что хотите удалить все сообщения?" "Are you sure you want to clear all messages?": "Вы уверены, что хотите удалить все сообщения?"
} }