Skip to content

Commit

Permalink
Merge pull request #38 from animalnots/dev
Browse files Browse the repository at this point in the history
Fix bettergpt import, added image zoom, model for title generation
  • Loading branch information
animalnots authored Aug 12, 2024
2 parents 134cc78 + ce3a542 commit f8ba356
Show file tree
Hide file tree
Showing 18 changed files with 285 additions and 58 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "better-chatgpt",
"private": true,
"version": "1.4.1",
"version": "1.5.0",
"type": "module",
"homepage": "./",
"main": "electron/index.cjs",
Expand Down
10 changes: 10 additions & 0 deletions public/locales/en/import.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"notifications": {
"invalidOpenAIDataFormat" : "Invalid openai data format",
"invalidChatsDataFormat" : "Invalid chats data format",
"invalidFormatForVersion" : "Invalid format for specified version",
"successfulImport" : "Succesfully imported!",
"unrecognisedDataFormat" : "Unrecognised data format. Supported formats are: BetterGPT export, OpenAI export, OpenAI Playground (JSON)"
}
}

1 change: 1 addition & 0 deletions public/locales/en/model.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"configuration": "Configuration",
"model": "Model",
"modelTitleGeneration": "Model for title generation",
"token": {
"label": "Max Token",
"description": "The maximum number of tokens to generate in the chat completion. The total length of input tokens and generated tokens is limited by the model's context length."
Expand Down
29 changes: 28 additions & 1 deletion src/components/Chat/ChatContent/Message/View/ContentView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import DeleteButton from './Button/DeleteButton';
import MarkdownModeButton from './Button/MarkdownModeButton';

import CodeBlock from '../CodeBlock';
import PopupModal from '@components/PopupModal';

const ContentView = memo(
({
Expand All @@ -53,6 +54,7 @@ const ContentView = memo(
const { handleSubmit } = useSubmit();

const [isDelete, setIsDelete] = useState<boolean>(false);
const [zoomedImage, setZoomedImage] = useState<string | null>(null);

const currentChatIndex = useStore((state) => state.currentChatIndex);
const setChats = useStore((state) => state.setChats);
Expand Down Expand Up @@ -108,6 +110,14 @@ const ContentView = memo(
navigator.clipboard.writeText((content[0] as TextContentInterface).text);
};

const handleImageClick = (imageUrl: string) => {
setZoomedImage(imageUrl);
};

const handleCloseZoom = () => {
setZoomedImage(null);
};

return (
<>
<div className='markdown prose w-full md:max-w-full break-words dark:prose-invert dark share-gpt-message'>
Expand Down Expand Up @@ -148,11 +158,28 @@ const ContentView = memo(
<img
src={image.image_url.url}
alt={`uploaded-${index}`}
className='h-20'
className='h-20 cursor-pointer'
onClick={() => handleImageClick(image.image_url.url)}
/>
</div>
))}
</div>
{zoomedImage && (
<PopupModal
title=''
setIsModalOpen={handleCloseZoom}
handleConfirm={handleCloseZoom}
cancelButton={false}
>
<div className='flex justify-center'>
<img
src={zoomedImage}
alt='Zoomed'
className='max-w-full max-h-full'
/>
</div>
</PopupModal>
)}
<div className='flex justify-end gap-2 w-full mt-2'>
{isDelete || (
<>
Expand Down
9 changes: 7 additions & 2 deletions src/components/Chat/ChatContent/Message/View/EditView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,17 @@ const EditView = ({
messageIndex: number;
sticky?: boolean;
}) => {
const setCurrentChatIndex = useStore((state) => state.setCurrentChatIndex);
const inputRole = useStore((state) => state.inputRole);
const setChats = useStore((state) => state.setChats);
const currentChatIndex = useStore((state) => state.currentChatIndex);
var currentChatIndex = useStore((state) => state.currentChatIndex);
const model = useStore((state) => {
const isInitialised =
state.chats && state.chats.length > 0 && state.currentChatIndex >= 0;
state.chats && state.chats.length > 0 && state.currentChatIndex >= 0 && state.currentChatIndex < state.chats.length;
if (!isInitialised) {
currentChatIndex = 0
setCurrentChatIndex(0)
}
return isInitialised
? state.chats![state.currentChatIndex].config.model
: defaultModel;
Expand Down
2 changes: 1 addition & 1 deletion src/components/ChatConfigMenu/ChatConfigMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const ChatConfigPopup = ({
_systemMessage={_systemMessage}
_setSystemMessage={_setSystemMessage}
/>
<ModelSelector _model={_model} _setModel={_setModel} />
<ModelSelector _model={_model} _setModel={_setModel} _label={t('model')} />
<MaxTokenSlider
_maxToken={_maxToken}
_setMaxToken={_setMaxToken}
Expand Down
11 changes: 10 additions & 1 deletion src/components/ConfigMenu/ConfigMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ const ConfigMenu = ({
handleClickBackdrop={handleConfirm}
>
<div className='p-6 border-b border-gray-200 dark:border-gray-600'>
<ModelSelector _model={_model} _setModel={_setModel} />
<ModelSelector
_model={_model}
_setModel={_setModel}
_label={t('Model')}
/>
<MaxTokenSlider
_maxToken={_maxToken}
_setMaxToken={_setMaxToken}
Expand All @@ -74,14 +78,19 @@ const ConfigMenu = ({
export const ModelSelector = ({
_model,
_setModel,
_label,
}: {
_model: ModelOptions;
_setModel: React.Dispatch<React.SetStateAction<ModelOptions>>;
_label: string;
}) => {
const [dropDown, setDropDown] = useState<boolean>(false);

return (
<div className='mb-4'>
<label className='block text-sm font-medium text-gray-900 dark:text-white'>
{_label}
</label>
<button
className='btn btn-neutral btn-small flex gap-1'
type='button'
Expand Down
64 changes: 58 additions & 6 deletions src/components/ImportExportChat/ImportChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ import { v4 as uuidv4 } from 'uuid';
import useStore from '@store/store';

import {
importOpenAIChatExport,
isLegacyImport,
isOpenAIContent,
validateAndFixChats,
validateExportV1,
} from '@utils/import';

import { ChatInterface, Folder, FolderCollection } from '@type/chat';
import { ExportBase } from '@type/export';
import { toast } from 'react-toastify';

const ImportChat = () => {
const { t } = useTranslation();
const { t } = useTranslation(['main', 'import']);
const setChats = useStore.getState().setChats;
const setFolders = useStore.getState().setFolders;
const inputRef = useRef<HTMLInputElement>(null);
Expand All @@ -26,7 +29,6 @@ const ImportChat = () => {
const handleFileUpload = () => {
if (!inputRef || !inputRef.current) return;
const file = inputRef.current.files?.[0];

if (file) {
const reader = new FileReader();

Expand All @@ -35,7 +37,24 @@ const ImportChat = () => {

try {
const parsedData = JSON.parse(data);
if (isLegacyImport(parsedData)) {
if (isOpenAIContent(parsedData)) {
try {
const chats = importOpenAIChatExport(parsedData);
const prevChats: ChatInterface[] = JSON.parse(
JSON.stringify(useStore.getState().chats)
);
setChats(chats.concat(prevChats));
toast.success(
t('notifications.successfulImport', { ns: 'import' })
);
} catch (error: unknown) {
toast.error(
`${t('notifications.invalidOpenAIDataFormat', {
ns: 'import',
})}: ${(error as Error).message}`
);
}
} else if (isLegacyImport(parsedData)) {
if (validateAndFixChats(parsedData)) {
// import new folders
const folderNameToIdMap: Record<string, string> = {};
Expand Down Expand Up @@ -84,10 +103,18 @@ const ImportChat = () => {
} else {
setChats(parsedData);
}
toast.success(
t('notifications.successfulImport', { ns: 'import' })
);
setAlert({ message: 'Succesfully imported!', success: true });
} else {
toast.error(
t('notifications.invalidChatsDataFormat', { ns: 'import' })
);
setAlert({
message: 'Invalid chats data format',
message: t('notifications.invalidChatsDataFormat', {
ns: 'import',
}),
success: false,
});
}
Expand Down Expand Up @@ -120,17 +147,42 @@ const ImportChat = () => {
}
}

setAlert({ message: 'Succesfully imported!', success: true });
toast.success(
t('notifications.successfulImport', { ns: 'import' })
);
setAlert({
message: t('notifications.successfulImport', {
ns: 'import',
}),
success: true,
});
} else {
toast.error(
t('notifications.invalidFormatForVersion', { ns: 'import' })
);
setAlert({
message: 'Invalid format',
message: t('notifications.invalidFormatForVersion', {
ns: 'import',
}),
success: false,
});
}
break;
default:
toast.error(
t('notifications.unrecognisedDataFormat', { ns: 'import' })
);
setAlert({
message: t('notifications.unrecognisedDataFormat', {
ns: 'import',
}),
success: false,
});
break;
}
}
} catch (error: unknown) {
toast.error((error as Error).message);
setAlert({ message: (error as Error).message, success: false });
}
};
Expand Down
1 change: 1 addition & 0 deletions src/components/ImportExportChat/ImportChatOpenAI.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// TODO: NOT USED, TO BE REMOVED R: KISS, DRY
import React, { useRef } from 'react';
import { useTranslation } from 'react-i18next';

Expand Down
2 changes: 1 addition & 1 deletion src/components/ImportExportChat/ImportExportChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const ImportExportChat = () => {
<ImportChat />
<ExportChat />
<div className='border-t my-3 border-gray-200 dark:border-gray-600' />
<ImportChatOpenAI setIsModalOpen={setIsModalOpen} />
{/* <ImportChatOpenAI setIsModalOpen={setIsModalOpen} /> */}
</div>
</PopupModal>
)}
Expand Down
14 changes: 12 additions & 2 deletions src/components/SettingsMenu/AutoTitleToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import useStore from '@store/store';
import Toggle from '@components/Toggle';
import { ModelSelector } from '@components/ConfigMenu/ConfigMenu';
import { ModelOptions } from '@type/chat';

const AutoTitleToggle = () => {
const { t } = useTranslation();
const { t } = useTranslation(['main', 'model']);

const setAutoTitle = useStore((state) => state.setAutoTitle);
const setTitleModel = useStore((state) => state.setTitleModel);
const [_model, _setModel] = useState<ModelOptions>(useStore.getState().titleModel);

const [isChecked, setIsChecked] = useState<boolean>(
useStore.getState().autoTitle
Expand All @@ -16,12 +20,18 @@ const AutoTitleToggle = () => {
setAutoTitle(isChecked);
}, [isChecked]);

return (
useEffect(() => {
setTitleModel(_model);
}, [_model]);

return (<>
<Toggle
label={t('autoTitle') as string}
isChecked={isChecked}
setIsChecked={setIsChecked}
/>
{isChecked ? <ModelSelector _model={_model} _setModel={_setModel} _label={t('modelTitleGeneration',{ ns:'model'})} />
: ''}</>
);
};

Expand Down
4 changes: 2 additions & 2 deletions src/constants/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const modelOptions: ModelOptions[] = [
];

export const defaultApiVersion = '2024-04-01-preview';
export const defaultModel = 'gpt-3.5-turbo';
export const defaultModel = 'gpt-4o-mini';

export const modelMaxToken: { [key: string]: number } = {
'gpt-3.5-turbo': 4096,
Expand Down Expand Up @@ -172,7 +172,7 @@ export const _defaultChatConfig: ConfigInterface = {
temperature: 1,
presence_penalty: 0,
top_p: 1,
frequency_penalty: 0,
frequency_penalty: 0
};

export const generateDefaultChat = (
Expand Down
15 changes: 11 additions & 4 deletions src/hooks/useSubmit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const useSubmit = () => {
const generating = useStore((state) => state.generating);
const currentChatIndex = useStore((state) => state.currentChatIndex);
const setChats = useStore((state) => state.setChats);

const generateTitle = async (
message: MessageInterface[],
modelConfig: ConfigInterface
Expand All @@ -34,22 +34,29 @@ const useSubmit = () => {
if (apiEndpoint === officialAPIEndpoint) {
throw new Error(t('noApiKeyWarning') as string);
}

const titleChatConfig = {
..._defaultChatConfig, // Spread the original config
model: useStore.getState().titleModel ?? _defaultChatConfig.model, // Override the model property
};
// other endpoints
data = await getChatCompletion(
useStore.getState().apiEndpoint,
message,
_defaultChatConfig,
titleChatConfig,
undefined,
undefined,
useStore.getState().apiVersion
);
} else if (apiKey) {
const titleChatConfig = {
...modelConfig, // Spread the original config
model: useStore.getState().titleModel ?? modelConfig.model, // Override the model property
};
// own apikey
data = await getChatCompletion(
useStore.getState().apiEndpoint,
message,
modelConfig,
titleChatConfig,
apiKey,
undefined,
useStore.getState().apiVersion
Expand Down
2 changes: 1 addition & 1 deletion src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import LanguageDetector from 'i18next-browser-languagedetector';

const googleClientId = import.meta.env.VITE_GOOGLE_CLIENT_ID || undefined;

const namespace = ['main', 'api', 'about', 'model'];
const namespace = ['main', 'api', 'about', 'model', 'import'];
if (googleClientId) namespace.push('drive');

i18n
Expand Down
Loading

0 comments on commit f8ba356

Please sign in to comment.