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:
parent
d8e3844fb9
commit
3ca503a3f2
|
@ -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,24 +111,31 @@ 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">
|
||||||
{t(
|
<div className="mb-2">
|
||||||
'Please set your OpenAI API key in the bottom left of the sidebar.',
|
{t(
|
||||||
)}
|
'Please set your OpenAI API key in the bottom left of the sidebar.',
|
||||||
</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: ")}
|
<div>
|
||||||
<a
|
{t(
|
||||||
href="https://platform.openai.com/account/api-keys"
|
"If you don't have an OpenAI API key, you can get one here: ",
|
||||||
target="_blank"
|
)}
|
||||||
rel="noreferrer"
|
<a
|
||||||
className="text-blue-500 hover:underline"
|
href="https://platform.openai.com/account/api-keys"
|
||||||
>
|
target="_blank"
|
||||||
openai.com
|
rel="noreferrer"
|
||||||
</a>
|
className="text-blue-500 hover:underline"
|
||||||
|
>
|
||||||
|
openai.com
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : modelError ? (
|
) : 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}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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">
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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">
|
||||||
<ClearConversations onClearConversations={onClearConversations} />
|
{conversationsCount > 0 ? (
|
||||||
|
<ClearConversations onClearConversations={onClearConversations} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
<Import onImport={onImportConversations} />
|
<Import onImport={onImportConversations} />
|
||||||
|
|
||||||
|
|
|
@ -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?": "Вы уверены, что хотите удалить все сообщения?"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue