This commit is contained in:
Mckay Wrigley 2023-04-18 08:29:30 -06:00
commit 1a9a7dcad0
53 changed files with 378 additions and 112 deletions

View File

@ -1,4 +1,4 @@
import { IconArrowDown, IconClearAll, IconSettings } from '@tabler/icons-react'; import { IconClearAll, IconSettings } from '@tabler/icons-react';
import { import {
MutableRefObject, MutableRefObject,
memo, memo,
@ -467,6 +467,11 @@ export const Chat = memo(({ stopConversationRef }: Props) => {
key={index} key={index}
message={message} message={message}
messageIndex={index} messageIndex={index}
onEdit={(editedMessage) => {
setCurrentMessage(editedMessage);
// discard edited message and the ones that come after then resend
handleSend(editedMessage, selectedConversation?.messages.length - index);
}}
/> />
))} ))}
@ -487,24 +492,16 @@ export const Chat = memo(({ stopConversationRef }: Props) => {
setCurrentMessage(message); setCurrentMessage(message);
handleSend(message, 0, plugin); handleSend(message, 0, plugin);
}} }}
onScrollDownClick={handleScrollDown}
onRegenerate={() => { onRegenerate={() => {
if (currentMessage) { if (currentMessage) {
handleSend(currentMessage, 2, null); handleSend(currentMessage, 2, null);
} }
}} }}
showScrollDownButton={showScrollDownButton}
/> />
</> </>
)} )}
{showScrollDownButton && (
<div className="absolute bottom-0 right-0 mb-4 mr-4 pb-20">
<button
className="flex h-7 w-7 items-center justify-center rounded-full bg-neutral-300 text-gray-800 shadow-md hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-neutral-200"
onClick={handleScrollDown}
>
<IconArrowDown size={18} />
</button>
</div>
)}
</div> </div>
); );
}); });

View File

@ -1,4 +1,5 @@
import { import {
IconArrowDown,
IconBolt, IconBolt,
IconBrandGoogle, IconBrandGoogle,
IconPlayerStop, IconPlayerStop,
@ -30,15 +31,19 @@ import { VariableModal } from './VariableModal';
interface Props { interface Props {
onSend: (message: Message, plugin: Plugin | null) => void; onSend: (message: Message, plugin: Plugin | null) => void;
onRegenerate: () => void; onRegenerate: () => void;
onScrollDownClick: () => void;
stopConversationRef: MutableRefObject<boolean>; stopConversationRef: MutableRefObject<boolean>;
textareaRef: MutableRefObject<HTMLTextAreaElement | null>; textareaRef: MutableRefObject<HTMLTextAreaElement | null>;
showScrollDownButton: boolean;
} }
export const ChatInput = ({ export const ChatInput = ({
onSend, onSend,
onRegenerate, onRegenerate,
onScrollDownClick,
stopConversationRef, stopConversationRef,
textareaRef, textareaRef,
showScrollDownButton
}: Props) => { }: Props) => {
const { t } = useTranslation('chat'); const { t } = useTranslation('chat');
@ -341,6 +346,17 @@ export const ChatInput = ({
)} )}
</button> </button>
{showScrollDownButton && (
<div className="absolute bottom-12 right-0 lg:bottom-0 lg:-right-10">
<button
className="flex h-7 w-7 items-center justify-center rounded-full bg-neutral-300 text-gray-800 shadow-md hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-neutral-200"
onClick={onScrollDownClick}
>
<IconArrowDown size={18} />
</button>
</div>
)}
{showPromptList && filteredPrompts.length > 0 && ( {showPromptList && filteredPrompts.length > 0 && (
<div className="absolute bottom-12 w-full"> <div className="absolute bottom-12 w-full">
<PromptList <PromptList

View File

@ -26,9 +26,10 @@ import remarkMath from 'remark-math';
interface Props { interface Props {
message: Message; message: Message;
messageIndex: number; messageIndex: number;
onEdit?: (editedMessage: Message) => void
} }
export const ChatMessage: FC<Props> = memo(({ message, messageIndex }) => { export const ChatMessage: FC<Props> = memo(({ message, messageIndex, onEdit }) => {
const { t } = useTranslation('chat'); const { t } = useTranslation('chat');
const { const {
@ -57,31 +58,8 @@ export const ChatMessage: FC<Props> = memo(({ message, messageIndex }) => {
const handleEditMessage = () => { const handleEditMessage = () => {
if (message.content != messageContent) { if (message.content != messageContent) {
if (selectedConversation) { if (selectedConversation && onEdit) {
const updatedMessages = selectedConversation.messages onEdit({ ...message, content: messageContent });
.map((m, i) => {
if (i < messageIndex) {
return m;
}
})
.filter((m) => m) as Message[];
const updatedConversation = {
...selectedConversation,
messages: updatedMessages,
};
const { single, all } = updateConversation(
updatedConversation,
conversations,
);
homeDispatch({ field: 'selectedConversation', value: single });
homeDispatch({ field: 'conversations', value: all });
homeDispatch({
field: 'currentMessage',
value: { ...message, content: messageContent },
});
} }
} }
setIsEditing(false); setIsEditing(false);

View File

@ -1,9 +1,11 @@
import { FC, useState } from 'react'; import { FC, useContext, useState } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { DEFAULT_TEMPERATURE } from '@/utils/app/const'; import { DEFAULT_TEMPERATURE } from '@/utils/app/const';
import HomeContext from '@/pages/api/home/home.context';
interface Props { interface Props {
label: string; label: string;
onChangeTemperature: (temperature: number) => void; onChangeTemperature: (temperature: number) => void;
@ -13,7 +15,13 @@ export const TemperatureSlider: FC<Props> = ({
label, label,
onChangeTemperature, onChangeTemperature,
}) => { }) => {
const [temperature, setTemperature] = useState(DEFAULT_TEMPERATURE); const {
state: { conversations },
} = useContext(HomeContext);
const lastConversation = conversations[conversations.length - 1];
const [temperature, setTemperature] = useState(
lastConversation?.temperature ?? DEFAULT_TEMPERATURE,
);
const { t } = useTranslation('chat'); const { t } = useTranslation('chat');
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = parseFloat(event.target.value); const newValue = parseFloat(event.target.value);
@ -31,7 +39,9 @@ export const TemperatureSlider: FC<Props> = ({
'Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.', 'Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.',
)} )}
</span> </span>
<span className="mt-2 mb-1 text-center">{temperature.toFixed(1)}</span> <span className="mt-2 mb-1 text-center text-neutral-900 dark:text-neutral-100">
{temperature.toFixed(1)}
</span>
<input <input
className="cursor-pointer" className="cursor-pointer"
type="range" type="range"
@ -41,7 +51,7 @@ export const TemperatureSlider: FC<Props> = ({
value={temperature} value={temperature}
onChange={handleChange} onChange={handleChange}
/> />
<ul className="w mt-2 pb-8 flex justify-between px-[24px]"> <ul className="w mt-2 pb-8 flex justify-between px-[24px] text-neutral-900 dark:text-neutral-100">
<li className="relative flex justify-center"> <li className="relative flex justify-center">
<span className="absolute">{t('Precise')}</span> <span className="absolute">{t('Precise')}</span>
</li> </li>

View File

@ -1,10 +1,17 @@
import { IconFileExport, IconMoon, IconSun } from '@tabler/icons-react'; import {
import { useContext } from 'react'; IconFileExport,
IconMoon,
IconSettings,
IconSun,
} from '@tabler/icons-react';
import { useContext, useState } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import HomeContext from '@/pages/api/home/home.context'; import HomeContext from '@/pages/api/home/home.context';
import { SettingDialog } from '@/components/Settings/SettingDialog';
import { Import } from '../../Settings/Import'; import { Import } from '../../Settings/Import';
import { Key } from '../../Settings/Key'; import { Key } from '../../Settings/Key';
import { SidebarButton } from '../../Sidebar/SidebarButton'; import { SidebarButton } from '../../Sidebar/SidebarButton';
@ -14,6 +21,7 @@ import { PluginKeys } from './PluginKeys';
export const ChatbarSettings = () => { export const ChatbarSettings = () => {
const { t } = useTranslation('sidebar'); const { t } = useTranslation('sidebar');
const [isSettingDialogOpen, setIsSettingDialog] = useState<boolean>(false);
const { const {
state: { state: {
@ -49,16 +57,9 @@ export const ChatbarSettings = () => {
/> />
<SidebarButton <SidebarButton
text={lightMode === 'light' ? t('Dark mode') : t('Light mode')} text={t('Settings')}
icon={ icon={<IconSettings size={18} />}
lightMode === 'light' ? <IconMoon size={18} /> : <IconSun size={18} /> onClick={() => setIsSettingDialog(true)}
}
onClick={() =>
homeDispatch({
field: 'lightMode',
value: lightMode === 'light' ? 'dark' : 'light',
})
}
/> />
{!serverSideApiKeyIsSet ? ( {!serverSideApiKeyIsSet ? (
@ -66,6 +67,13 @@ export const ChatbarSettings = () => {
) : null} ) : null}
{!serverSidePluginKeysSet ? <PluginKeys /> : null} {!serverSidePluginKeysSet ? <PluginKeys /> : null}
<SettingDialog
open={isSettingDialogOpen}
onClose={() => {
setIsSettingDialog(false);
}}
/>
</div> </div>
); );
}; };

View File

@ -0,0 +1,105 @@
import { FC, useContext, useEffect, useReducer, useRef } from 'react';
import { useTranslation } from 'next-i18next';
import { useCreateReducer } from '@/hooks/useCreateReducer';
import { getSettings, saveSettings } from '@/utils/app/settings';
import { Settings } from '@/types/settings';
import HomeContext from '@/pages/api/home/home.context';
interface Props {
open: boolean;
onClose: () => void;
}
export const SettingDialog: FC<Props> = ({ open, onClose }) => {
const { t } = useTranslation('settings');
const settings: Settings = getSettings();
const { state, dispatch } = useCreateReducer<Settings>({
initialState: settings,
});
const { dispatch: homeDispatch } = useContext(HomeContext);
const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleMouseDown = (e: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
window.addEventListener('mouseup', handleMouseUp);
}
};
const handleMouseUp = (e: MouseEvent) => {
window.removeEventListener('mouseup', handleMouseUp);
onClose();
};
window.addEventListener('mousedown', handleMouseDown);
return () => {
window.removeEventListener('mousedown', handleMouseDown);
};
}, [onClose]);
const handleSave = () => {
homeDispatch({ field: 'lightMode', value: state.theme });
saveSettings(state);
};
// Render nothing if the dialog is not open.
if (!open) {
return <></>;
}
// Render the dialog.
return (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
<div className="fixed inset-0 z-10 overflow-hidden">
<div className="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
<div
className="hidden sm:inline-block sm:h-screen sm:align-middle"
aria-hidden="true"
/>
<div
ref={modalRef}
className="dark:border-netural-400 inline-block max-h-[400px] transform overflow-y-auto rounded-lg border border-gray-300 bg-white px-4 pt-5 pb-4 text-left align-bottom shadow-xl transition-all dark:bg-[#202123] sm:my-8 sm:max-h-[600px] sm:w-full sm:max-w-lg sm:p-6 sm:align-middle"
role="dialog"
>
<div className="text-lg pb-4 font-bold text-black dark:text-neutral-200">
{t('Settings')}
</div>
<div className="text-sm font-bold mb-2 text-black dark:text-neutral-200">
{t('Theme')}
</div>
<select
className="w-full cursor-pointer bg-transparent p-2 text-neutral-700 dark:text-neutral-200"
value={state.theme}
onChange={(event) =>
dispatch({ field: 'theme', value: event.target.value })
}
>
<option value="dark">{t('Dark mode')}</option>
<option value="light">{t('Light mode')}</option>
</select>
<button
type="button"
className="w-full px-4 py-2 mt-6 border rounded-lg shadow border-neutral-500 text-neutral-900 hover:bg-neutral-100 focus:outline-none dark:border-neutral-800 dark:border-opacity-50 dark:bg-white dark:text-black dark:hover:bg-neutral-300"
onClick={() => {
handleSave();
onClose();
}}
>
{t('Save')}
</button>
</div>
</div>
</div>
</div>
);
};

View File

@ -21,6 +21,7 @@ module.exports = {
"vi", "vi",
"zh", "zh",
"ar", "ar",
"tr",
], ],
}, },
localePath: localePath:

View File

@ -23,6 +23,7 @@ import {
} from '@/utils/app/conversation'; } from '@/utils/app/conversation';
import { saveFolders } from '@/utils/app/folders'; import { saveFolders } from '@/utils/app/folders';
import { savePrompts } from '@/utils/app/prompts'; import { savePrompts } from '@/utils/app/prompts';
import { getSettings } from '@/utils/app/settings';
import { Conversation } from '@/types/chat'; import { Conversation } from '@/types/chat';
import { KeyValuePair } from '@/types/data'; import { KeyValuePair } from '@/types/data';
@ -68,7 +69,7 @@ const Home = ({
conversations, conversations,
selectedConversation, selectedConversation,
prompts, prompts,
temperature temperature,
}, },
dispatch, dispatch,
} = contextValue; } = contextValue;
@ -191,7 +192,7 @@ const Home = ({
tokenLimit: OpenAIModels[defaultModelId].tokenLimit, tokenLimit: OpenAIModels[defaultModelId].tokenLimit,
}, },
prompt: DEFAULT_SYSTEM_PROMPT, prompt: DEFAULT_SYSTEM_PROMPT,
temperature: DEFAULT_TEMPERATURE, temperature: lastConversation?.temperature ?? DEFAULT_TEMPERATURE,
folderId: null, folderId: null,
}; };
@ -250,9 +251,12 @@ const Home = ({
// ON LOAD -------------------------------------------- // ON LOAD --------------------------------------------
useEffect(() => { useEffect(() => {
const theme = localStorage.getItem('theme'); const settings = getSettings();
if (theme) { if (settings.theme) {
dispatch({ field: 'lightMode', value: theme as 'dark' | 'light' }); dispatch({
field: 'lightMode',
value: settings.theme,
});
} }
const apiKey = localStorage.getItem('apiKey'); const apiKey = localStorage.getItem('apiKey');
@ -322,6 +326,7 @@ const Home = ({
value: cleanedSelectedConversation, value: cleanedSelectedConversation,
}); });
} else { } else {
const lastConversation = conversations[conversations.length - 1];
dispatch({ dispatch({
field: 'selectedConversation', field: 'selectedConversation',
value: { value: {
@ -330,7 +335,7 @@ const Home = ({
messages: [], messages: [],
model: OpenAIModels[defaultModelId], model: OpenAIModels[defaultModelId],
prompt: DEFAULT_SYSTEM_PROMPT, prompt: DEFAULT_SYSTEM_PROMPT,
temperature: DEFAULT_TEMPERATURE, temperature: lastConversation?.temperature ?? DEFAULT_TEMPERATURE,
folderId: null, folderId: null,
}, },
}); });
@ -419,6 +424,7 @@ export const getServerSideProps: GetServerSideProps = async ({ locale }) => {
'sidebar', 'sidebar',
'markdown', 'markdown',
'promptbar', 'promptbar',
'settings',
])), ])),
}, },
}; };

View File

@ -0,0 +1,4 @@
{
"Dark mode": "الوضع الداكن",
"Light mode": "الوضع الفاتح"
}

View File

@ -0,0 +1,4 @@
{
"Dark mode": "ডার্ক মোড",
"Light mode": "লাইট মোড"
}

View File

@ -7,7 +7,5 @@
"Import data": "আলাপচারিতা ইমপোর্ট", "Import data": "আলাপচারিতা ইমপোর্ট",
"Are you sure?": "আপনি কি নিশ্চিত?", "Are you sure?": "আপনি কি নিশ্চিত?",
"Clear conversations": "কথোপকথন পরিষ্কার করুন", "Clear conversations": "কথোপকথন পরিষ্কার করুন",
"Export data": "আলাপচারিতা এক্সপোর্ট", "Export data": "আলাপচারিতা এক্সপোর্ট"
"Dark mode": "ডার্ক মোড",
"Light mode": "লাইট মোড"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "Dark Mode",
"Light mode": "Light Mode"
}

View File

@ -7,7 +7,5 @@
"Import data": "Konversationen importieren", "Import data": "Konversationen importieren",
"Are you sure?": "Bist du sicher?", "Are you sure?": "Bist du sicher?",
"Clear conversations": "Konversationen löschen", "Clear conversations": "Konversationen löschen",
"Export data": "Konversationen exportieren", "Export data": "Konversationen exportieren"
"Dark mode": "Dark Mode",
"Light mode": "Light Mode"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "Modo oscuro",
"Light mode": "Modo claro"
}

View File

@ -7,7 +7,5 @@
"Import data": "Importar conversaciones", "Import data": "Importar conversaciones",
"Are you sure?": "¿Estás seguro?", "Are you sure?": "¿Estás seguro?",
"Clear conversations": "Borrar conversaciones", "Clear conversations": "Borrar conversaciones",
"Export data": "Exportar conversaciones", "Export data": "Exportar conversaciones"
"Dark mode": "Modo oscuro",
"Light mode": "Modo claro"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "Mode sombre",
"Light mode": "Mode clair"
}

View File

@ -7,7 +7,5 @@
"Import data": "Importer des conversations", "Import data": "Importer des conversations",
"Are you sure?": "Êtes-vous sûr ?", "Are you sure?": "Êtes-vous sûr ?",
"Clear conversations": "Effacer les conversations", "Clear conversations": "Effacer les conversations",
"Export data": "Exporter les conversations", "Export data": "Exporter les conversations"
"Dark mode": "Mode sombre",
"Light mode": "Mode clair"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "מצב כהה",
"Light mode": "מצב בהיר"
}

View File

@ -7,7 +7,5 @@
"Import data": "ייבוא שיחות", "Import data": "ייבוא שיחות",
"Are you sure?": "אתה בטוח?", "Are you sure?": "אתה בטוח?",
"Clear conversations": "ניקוי שיחות", "Clear conversations": "ניקוי שיחות",
"Export data": "ייצוא שיחות", "Export data": "ייצוא שיחות"
"Dark mode": "מצב כהה",
"Light mode": "מצב בהיר"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "Mode gelap",
"Light mode": "Mode terang"
}

View File

@ -7,7 +7,5 @@
"Import data": "Impor percakapan", "Import data": "Impor percakapan",
"Are you sure?": "Apakah Anda yakin?", "Are you sure?": "Apakah Anda yakin?",
"Clear conversations": "Hapus percakapan", "Clear conversations": "Hapus percakapan",
"Export data": "Ekspor percakapan", "Export data": "Ekspor percakapan"
"Dark mode": "Mode gelap",
"Light mode": "Mode terang"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "Modalità scura",
"Light mode": "Modalità chiara"
}

View File

@ -7,7 +7,5 @@
"Import data": "Importa dati", "Import data": "Importa dati",
"Are you sure?": "Sei sicuro?", "Are you sure?": "Sei sicuro?",
"Clear conversations": "Elimina conversazioni", "Clear conversations": "Elimina conversazioni",
"Export data": "Esporta dati", "Export data": "Esporta dati"
"Dark mode": "Modalità scura",
"Light mode": "Modalità chiara"
} }

View File

@ -0,0 +1,7 @@
{
"Settings": "設定",
"Theme": "テーマ",
"Save": "保存",
"Dark mode": "ダークモード",
"Light mode": "ライトモード"
}

View File

@ -9,5 +9,6 @@
"Clear conversations": " 会話をクリア", "Clear conversations": " 会話をクリア",
"Export data": "会話履歴をエクスポート", "Export data": "会話履歴をエクスポート",
"Dark mode": "ダークモード", "Dark mode": "ダークモード",
"Light mode": "ライトモード" "Light mode": "ライトモード",
"Settings": "設定"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "다크 모드",
"Light mode": "라이트 모드"
}

View File

@ -7,7 +7,5 @@
"Import data": "대화 가져오기", "Import data": "대화 가져오기",
"Are you sure?": "확실합니까?", "Are you sure?": "확실합니까?",
"Clear conversations": "대화 지우기", "Clear conversations": "대화 지우기",
"Export data": "대화 내보내기", "Export data": "대화 내보내기"
"Dark mode": "다크 모드",
"Light mode": "라이트 모드"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "Tryb ciemny",
"Light mode": "Tryb jasny"
}

View File

@ -7,7 +7,5 @@
"Import data": "Import rozmów", "Import data": "Import rozmów",
"Are you sure?": "Czy jesteś pewien?", "Are you sure?": "Czy jesteś pewien?",
"Clear conversations": "Usuń rozmowy", "Clear conversations": "Usuń rozmowy",
"Export data": "Eksport rozmów", "Export data": "Eksport rozmów"
"Dark mode": "Tryb ciemny",
"Light mode": "Tryb jasny"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "Modo escuro",
"Light mode": "Modo claro"
}

View File

@ -7,7 +7,5 @@
"Import data": "Importar conversas", "Import data": "Importar conversas",
"Are you sure?": "Tem certeza?", "Are you sure?": "Tem certeza?",
"Clear conversations": "Apagar conversas", "Clear conversations": "Apagar conversas",
"Export data": "Exportar conversas", "Export data": "Exportar conversas"
"Dark mode": "Modo escuro",
"Light mode": "Modo claro"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "Modul întunecat",
"Light mode": "Modul de golire"
}

View File

@ -7,7 +7,5 @@
"Import data": "Importați conversații", "Import data": "Importați conversații",
"Are you sure?": "Esti sigur?", "Are you sure?": "Esti sigur?",
"Clear conversations": "Ștergeți conversațiile", "Clear conversations": "Ștergeți conversațiile",
"Export data": "Exportați conversații", "Export data": "Exportați conversații"
"Dark mode": "Modul întunecat",
"Light mode": "Modul de golire"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "Темный режим",
"Light mode": "Светлый режим"
}

View File

@ -7,7 +7,5 @@
"Import data": "Импортировать чаты", "Import data": "Импортировать чаты",
"Are you sure?": "Вы уверены?", "Are you sure?": "Вы уверены?",
"Clear conversations": "Удалить чаты", "Clear conversations": "Удалить чаты",
"Export data": "Экспортировать чаты", "Export data": "Экспортировать чаты"
"Dark mode": "Темный режим",
"Light mode": "Светлый режим"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "අඳුරු මාදිලිය",
"Light mode": "ආලෝක මාදිලිය"
}

View File

@ -7,7 +7,5 @@
"Import data": "සංවාද ආයාත කරන්න", "Import data": "සංවාද ආයාත කරන්න",
"Are you sure?": "ඔබට විශ්වාස ද?", "Are you sure?": "ඔබට විශ්වාස ද?",
"Clear conversations": "සංවාද මකන්න", "Clear conversations": "සංවාද මකන්න",
"Export data": "සංවාද නිර්යාත කරන්න", "Export data": "සංවාද නිර්යාත කරන්න"
"Dark mode": "අඳුරු මාදිලිය",
"Light mode": "ආලෝක මාදිලිය"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "Mörkt läge",
"Light mode": "Ljust läge"
}

View File

@ -7,7 +7,5 @@
"Import data": "Importera konversationer", "Import data": "Importera konversationer",
"Are you sure?": "Är du säker?", "Are you sure?": "Är du säker?",
"Clear conversations": "Radera konversationer", "Clear conversations": "Radera konversationer",
"Export data": "Exportera konversationer", "Export data": "Exportera konversationer"
"Dark mode": "Mörkt läge",
"Light mode": "Ljust läge"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "డార్క్ మోడ్",
"Light mode": "లైట్ మోడ్"
}

View File

@ -7,7 +7,5 @@
"Import data": "సంభాషణలు దిగుమతి చేయండి", "Import data": "సంభాషణలు దిగుమతి చేయండి",
"Are you sure?": "మీరు ఖచ్చితంగా ఉన్నారా?", "Are you sure?": "మీరు ఖచ్చితంగా ఉన్నారా?",
"Clear conversations": "సంభాషణలు తొలగించు", "Clear conversations": "సంభాషణలు తొలగించు",
"Export data": "సంభాషణలు ఎగుమతి చేయండి", "Export data": "సంభాషణలు ఎగుమతి చేయండి"
"Dark mode": "డార్క్ మోడ్",
"Light mode": "లైట్ మోడ్"
} }

View File

@ -0,0 +1,28 @@
{
"OpenAI API Key Required": "OpenAI API Anahtarı Gerekli",
"Please set your OpenAI API key in the bottom left of the sidebar.": "Lütfen OpenAI API anahtarınızı yan çubuğun sol altınan'dan ayarlayın.",
"Stop Generating": "Durdur",
"Prompt limit is {{maxLength}} characters": "Prompt sınırı {{maxLength}} karakterdir",
"System Prompt": "Sistem Prompt",
"You are ChatGPT, a large language model trained by OpenAI. Follow the user's instructions carefully. Respond using markdown.": "Sen ChatGPT'sin, OpenAI tarafından eğitilmiş büyük bir dil modelisin. Kullanıcının talimatlarını dikkatlice takip et. Yanıtını markdown kullanarak ver.",
"Enter a prompt": "Bir prompt girin",
"Regenerate response": "Yanıtı Yeniden Oluştur",
"Sorry, there was an error.": "Üzgünüm, bir hata oluştu.",
"Model": "Model",
"Conversation": "Sohbet",
"OR": "VEYA",
"Loading...": "Yükleniyor...",
"Type a message...": "Bir mesaj yazın...",
"Error fetching models.": "Modeller getirilirken hata oluştu.",
"AI": "Yapay Zeka",
"You": "Sen",
"Cancel": "İptal",
"Save & Submit": "Kaydet ve Gönder",
"Make sure your OpenAI API key is set in the bottom left of the sidebar.": "OpenAI API anahtarınızın yan çubuğun sol altında ayarlandığından emin ol.",
"If you completed this step, OpenAI may be experiencing issues.": "Bu adımı tamamladıysanız, OpenAI sorun yaşıyor olabilir.",
"click if using a .env.local file": "Eğer .env.local dosyası kullanıyorsanız tıklayın",
"Message limit is {{maxLength}} characters. You have entered {{valueLength}} characters.": "Mesaj sınırı {{maxLength}} karakterdir. {{valueLength}} karakter girdiniz.",
"Please enter a message": "Lütfen bir mesaj girin",
"Chatbot UI is an advanced chatbot kit for OpenAI's chat models aiming to mimic ChatGPT's interface and functionality.": "Chatbot UI, ChatGPT'nin arayüz ve işlevselliğini taklit etmeyi amaçlayan OpenAI'in sohbet modelleri için gelişmiş bir sohbetbot kitidir.",
"Are you sure you want to clear all messages?": "Tüm mesajları temizlemek istediğinize emin misiniz?"
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,5 @@
{
"Copy code": "Kodu kopyala",
"Copied!": "Kopyalandi!",
"Enter file name": "Dosya ismi gir"
}

View File

@ -0,0 +1,12 @@
{
"New prompt": "Yeni prompt",
"New folder": "Yeni klasör",
"No prompts.": "Prompt yok.",
"Search prompts...": "Prompt'ları ara...",
"Name": "İsim",
"Description": "Açıklama",
"A description for your prompt.": "Prompt'unuz için bir açıklama.",
"Prompt": "Prompt",
"Prompt content. Use {{}} to denote a variable. Ex: {{name}} is a {{adjective}} {{noun}}": "Prompt içeriği. Değişken belirtmek için {{}} kullanın. Örn: {{ad}} bir {{sıfat}} {{isim}}",
"Save": "Kaydet"
}

View File

@ -0,0 +1,14 @@
{
"New folder": "Yeni klasör",
"New chat": "Yeni sohbet",
"No conversations.": "Hiçbir konuşma yok.",
"Search conversations...": "Sohbetleri ara...",
"OpenAI API Key": "OpenAI API Anahtarı",
"Import data": "Veri içe aktar",
"Are you sure?": "Emin misiniz?",
"Clear conversations": "Konuşmaları temizle",
"Export data": "Veri dışa aktar",
"Dark mode": "Karanlık mod",
"Light mode": "Aydınlık mod",
"Plugin Keys": "Plugin Anahtarları"
}

View File

@ -0,0 +1,4 @@
{
"Dark mode": "Chế độ tối",
"Light mode": "Chế độ sáng"
}

View File

@ -7,7 +7,5 @@
"Import data": "Nhập dữ liệu hội thoại", "Import data": "Nhập dữ liệu hội thoại",
"Are you sure?": "Bạn chắc chắn chứ?", "Are you sure?": "Bạn chắc chắn chứ?",
"Clear conversations": "Xoá các đoạn hội thoại", "Clear conversations": "Xoá các đoạn hội thoại",
"Export data": "Xuất dữ liệu hội thoại", "Export data": "Xuất dữ liệu hội thoại"
"Dark mode": "Chế độ tối",
"Light mode": "Chế độ sáng"
} }

View File

@ -0,0 +1,4 @@
{
"Dark mode": "深色模式",
"Light mode": "浅色模式"
}

View File

@ -7,7 +7,5 @@
"Import data": "导入对话", "Import data": "导入对话",
"Are you sure?": "确定吗?", "Are you sure?": "确定吗?",
"Clear conversations": "清空对话", "Clear conversations": "清空对话",
"Export data": "导出对话", "Export data": "导出对话"
"Dark mode": "深色模式",
"Light mode": "浅色模式"
} }

3
types/settings.ts Normal file
View File

@ -0,0 +1,3 @@
export interface Settings {
theme: 'light' | 'dark';
}

View File

@ -8,6 +8,7 @@ export const cleanSelectedConversation = (conversation: Conversation) => {
// added system prompt for each conversation (3/21/23) // added system prompt for each conversation (3/21/23)
// added folders (3/23/23) // added folders (3/23/23)
// added prompts (3/26/23) // added prompts (3/26/23)
// added messages (4/16/23)
let updatedConversation = conversation; let updatedConversation = conversation;
@ -41,6 +42,13 @@ export const cleanSelectedConversation = (conversation: Conversation) => {
}; };
} }
if (!updatedConversation.messages) {
updatedConversation = {
...updatedConversation,
messages: updatedConversation.messages || [],
};
}
return updatedConversation; return updatedConversation;
}; };
@ -49,6 +57,7 @@ export const cleanConversationHistory = (history: any[]): Conversation[] => {
// added system prompt for each conversation (3/21/23) // added system prompt for each conversation (3/21/23)
// added folders (3/23/23) // added folders (3/23/23)
// added prompts (3/26/23) // added prompts (3/26/23)
// added messages (4/16/23)
if (!Array.isArray(history)) { if (!Array.isArray(history)) {
console.warn('history is not an array. Returning an empty array.'); console.warn('history is not an array. Returning an empty array.');
@ -73,6 +82,10 @@ export const cleanConversationHistory = (history: any[]): Conversation[] => {
conversation.folderId = null; conversation.folderId = null;
} }
if (!conversation.messages) {
conversation.messages = [];
}
acc.push(conversation); acc.push(conversation);
return acc; return acc;
} catch (error) { } catch (error) {

23
utils/app/settings.ts Normal file
View File

@ -0,0 +1,23 @@
import { Settings } from '@/types/settings';
const STORAGE_KEY = 'settings';
export const getSettings = (): Settings => {
let settings: Settings = {
theme: 'dark',
};
const settingsJson = localStorage.getItem(STORAGE_KEY);
if (settingsJson) {
try {
let savedSettings = JSON.parse(settingsJson) as Settings;
settings = Object.assign(settings, savedSettings);
} catch (e) {
console.error(e);
}
}
return settings;
};
export const saveSettings = (settings: Settings) => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
};