Skip to content

Commit 177a8a8

Browse files
authored
Merge pull request #1 from animalnots/dev
Added vision capabilities / image support
2 parents fbc47eb + eb74dcb commit 177a8a8

File tree

24 files changed

+575
-159
lines changed

24 files changed

+575
-159
lines changed

README.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,20 +144,23 @@ One click deploy with Vercel
144144
5. Launch the app by running `yarn dev` or `npm run dev`
145145

146146
### Running it locally using docker compose
147+
147148
1. Ensure that you have the following installed:
148149

149150
- [docker](https://www.docker.com/) (v24.0.7 or above)
150-
```bash
151-
curl https://get.docker.com | sh \
152-
&& sudo usermod -aG docker $USER
153-
```
151+
```bash
152+
curl https://get.docker.com | sh \
153+
&& sudo usermod -aG docker $USER
154+
```
154155

155156
2. Build the docker image
157+
156158
```
157159
docker compose build
158160
```
159161
160162
3. Build and start the container using docker compose
163+
161164
```
162165
docker compose build
163166
docker compose up -d
@@ -168,6 +171,24 @@ One click deploy with Vercel
168171
docker compose down
169172
```
170173
174+
### Running it locally via desktop app
175+
176+
1. Ensure that you have the following installed:
177+
178+
- [yarn](https://yarnpkg.com/) or [npm](https://www.npmjs.com/) (6.14.15 or above)
179+
180+
2. Build the executable (Windows)
181+
182+
```
183+
yarn make --win
184+
```
185+
186+
3. Build for other OS
187+
```
188+
yarn make _ADD_BUILD_ARGS_HERE
189+
```
190+
To find out available building arguments, go to [electron-builder reference](https://www.electron.build/cli.html)
191+
171192
# ⭐️ Star History
172193
173194
[![Star History Chart](https://api.star-history.com/svg?repos=ztjhz/BetterChatGPT&type=Date)](https://github.com/ztjhz/BetterChatGPT/stargazers)

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"preview": "vite preview",
1414
"electron": "concurrently -k \"BROWSER=none yarn dev\" \"wait-on tcp:5173 && electron .\"",
1515
"pack": "yarn build && electron-builder --dir",
16-
"make": "yarn build && electron-builder"
16+
"make": "yarn build && electron-builder",
17+
"debug": "concurrently -k \"cross-env BROWSER=none yarn dev\" \"wait-on tcp:5173 && electron --inspect=5858 .\""
1718
},
1819
"build": {
1920
"appId": "better-chatgpt",
@@ -62,6 +63,7 @@
6263
"react-i18next": "^12.2.0",
6364
"react-markdown": "^8.0.5",
6465
"react-scroll-to-bottom": "^4.2.0",
66+
"react-toastify": "^10.0.5",
6567
"rehype-highlight": "^6.0.0",
6668
"rehype-katex": "^6.0.2",
6769
"remark-gfm": "^3.0.1",
@@ -80,6 +82,7 @@
8082
"@vitejs/plugin-react-swc": "^3.0.0",
8183
"autoprefixer": "^10.4.13",
8284
"concurrently": "^8.0.1",
85+
"cross-env": "^7.0.3",
8386
"electron": "^23.2.0",
8487
"electron-builder": "^23.6.0",
8588
"postcss": "^8.4.21",

public/locales/en/api.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
"howTo": "Get your personal API key <0>here</0>.",
1010
"inputLabel": "API Key"
1111
},
12+
"apiVersion": {
13+
"inputLabel": "Api Version",
14+
"description": "Api Version e.g. 2024-04-01-preview"
15+
},
1216
"customEndpoint": "Use custom API endpoint",
1317
"advancedConfig": "View advanced API configuration <0>here</0>",
1418
"noApiKeyWarning": "No API key supplied! Please check your API settings."

src/App.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { ChatInterface } from '@type/chat';
1010
import { Theme } from '@type/theme';
1111
import ApiPopup from '@components/ApiPopup';
1212
import Toast from '@components/Toast';
13+
import { ToastContainer } from 'react-toastify';
14+
import 'react-toastify/dist/ReactToastify.css';
1315

1416
function App() {
1517
const initialiseNewChat = useInitialiseNewChat();
@@ -80,6 +82,7 @@ function App() {
8082
<Chat />
8183
<ApiPopup />
8284
<Toast />
85+
<ToastContainer />
8386
</div>
8487
);
8588
}

src/api/api.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { ShareGPTSubmitBodyInterface } from '@type/api';
2-
import { ConfigInterface, MessageInterface, ModelOptions } from '@type/chat';
2+
import {
3+
ConfigInterface,
4+
ImageContentInterface,
5+
MessageInterface,
6+
ModelOptions,
7+
} from '@type/chat';
38
import { isAzureEndpoint } from '@utils/api';
49

510
export const getChatCompletion = async (
611
endpoint: string,
712
messages: MessageInterface[],
813
config: ConfigInterface,
914
apiKey?: string,
10-
customHeaders?: Record<string, string>
15+
customHeaders?: Record<string, string>,
16+
apiVersionToUse?: string
1117
) => {
1218
const headers: HeadersInit = {
1319
'Content-Type': 'application/json',
@@ -29,9 +35,10 @@ export const getChatCompletion = async (
2935

3036
// set api version to 2023-07-01-preview for gpt-4 and gpt-4-32k, otherwise use 2023-03-15-preview
3137
const apiVersion =
32-
model === 'gpt-4' || model === 'gpt-4-32k'
38+
apiVersionToUse ??
39+
(model === 'gpt-4' || model === 'gpt-4-32k'
3340
? '2023-07-01-preview'
34-
: '2023-03-15-preview';
41+
: '2023-03-15-preview');
3542

3643
const path = `openai/deployments/${model}/chat/completions?api-version=${apiVersion}`;
3744

@@ -42,6 +49,7 @@ export const getChatCompletion = async (
4249
endpoint += path;
4350
}
4451
}
52+
endpoint = endpoint.trim();
4553

4654
const response = await fetch(endpoint, {
4755
method: 'POST',
@@ -63,7 +71,8 @@ export const getChatCompletionStream = async (
6371
messages: MessageInterface[],
6472
config: ConfigInterface,
6573
apiKey?: string,
66-
customHeaders?: Record<string, string>
74+
customHeaders?: Record<string, string>,
75+
apiVersionToUse?: string
6776
) => {
6877
const headers: HeadersInit = {
6978
'Content-Type': 'application/json',
@@ -83,9 +92,10 @@ export const getChatCompletionStream = async (
8392

8493
// set api version to 2023-07-01-preview for gpt-4 and gpt-4-32k, otherwise use 2023-03-15-preview
8594
const apiVersion =
86-
model === 'gpt-4' || model === 'gpt-4-32k'
95+
apiVersionToUse ??
96+
(model === 'gpt-4' || model === 'gpt-4-32k'
8797
? '2023-07-01-preview'
88-
: '2023-03-15-preview';
98+
: '2023-03-15-preview');
8999
const path = `openai/deployments/${model}/chat/completions?api-version=${apiVersion}`;
90100

91101
if (!endpoint.endsWith(path)) {
@@ -95,7 +105,7 @@ export const getChatCompletionStream = async (
95105
endpoint += path;
96106
}
97107
}
98-
108+
endpoint = endpoint.trim();
99109
const response = await fetch(endpoint, {
100110
method: 'POST',
101111
headers,

src/components/ApiMenu/ApiMenu.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,20 @@ const ApiMenu = ({
2121
const setApiKey = useStore((state) => state.setApiKey);
2222
const apiEndpoint = useStore((state) => state.apiEndpoint);
2323
const setApiEndpoint = useStore((state) => state.setApiEndpoint);
24+
const apiVersion = useStore((state) => state.apiVersion);
25+
const setApiVersion = useStore((state) => state.setApiVersion);
2426

2527
const [_apiKey, _setApiKey] = useState<string>(apiKey || '');
2628
const [_apiEndpoint, _setApiEndpoint] = useState<string>(apiEndpoint);
2729
const [_customEndpoint, _setCustomEndpoint] = useState<boolean>(
2830
!availableEndpoints.includes(apiEndpoint)
2931
);
32+
const [_apiVersion, _setApiVersion] = useState<string>(apiVersion || '');
3033

3134
const handleSave = () => {
3235
setApiKey(_apiKey);
3336
setApiEndpoint(_apiEndpoint);
37+
setApiVersion(_apiVersion);
3438
setIsModalOpen(false);
3539
};
3640

@@ -92,6 +96,20 @@ const ApiMenu = ({
9296
/>
9397
</div>
9498

99+
<div className='flex gap-2 items-center justify-center mt-2'>
100+
<div className='min-w-fit text-gray-900 dark:text-gray-300 text-sm'>
101+
{t('apiVersion.inputLabel', { ns: 'api' })}
102+
</div>
103+
<input
104+
type='text'
105+
placeholder={t('apiVersion.description', { ns: 'api' }) ?? ''}
106+
className='text-gray-800 dark:text-white p-3 text-sm border-none bg-gray-200 dark:bg-gray-600 rounded-md m-0 w-full mr-0 h-8 focus:outline-none'
107+
value={_apiVersion}
108+
onChange={(e) => {
109+
_setApiVersion(e.target.value);
110+
}}
111+
/>
112+
</div>
95113
<div className='min-w-fit text-gray-900 dark:text-gray-300 text-sm flex flex-col gap-3 leading-relaxed'>
96114
<p className='mt-4'>
97115
<Trans

src/components/Chat/ChatContent/ChatContent.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import useSubmit from '@hooks/useSubmit';
1212
import DownloadChat from './DownloadChat';
1313
import CloneChat from './CloneChat';
1414
import ShareGPT from '@components/ShareGPT';
15+
import { ImageContentInterface, TextContentInterface } from '@type/chat';
1516

1617
const ChatContent = () => {
1718
const inputRole = useStore((state) => state.inputRole);
@@ -79,7 +80,10 @@ const ChatContent = () => {
7980

8081
<Message
8182
role={inputRole}
82-
content=''
83+
// For now we always initizlize a new message with an empty text content.
84+
// It is possible to send a message to the API without a TextContentInterface,
85+
// but the UI would need to be modified to allow the user to control the order of text and image content
86+
content={[{type: 'text', text: ''} as TextContentInterface]}
8387
messageIndex={stickyIndex}
8488
sticky
8589
/>

src/components/Chat/ChatContent/Message/CommandPrompt/CommandPrompt.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import { matchSorter } from 'match-sorter';
66
import { Prompt } from '@type/prompt';
77

88
import useHideOnOutsideClick from '@hooks/useHideOnOutsideClick';
9+
import { ContentInterface } from '@type/chat';
910

1011
const CommandPrompt = ({
1112
_setContent,
1213
}: {
13-
_setContent: React.Dispatch<React.SetStateAction<string>>;
14+
_setContent: React.Dispatch<React.SetStateAction<ContentInterface[]>>;
1415
}) => {
1516
const { t } = useTranslation();
1617
const prompts = useStore((state) => state.prompts);
@@ -69,7 +70,7 @@ const CommandPrompt = ({
6970
<li
7071
className='px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white cursor-pointer text-start w-full'
7172
onClick={() => {
72-
_setContent((prev) => prev + cp.prompt);
73+
_setContent((prev) => [{type: 'text', text: prev + cp.prompt}, ...prev.slice(1)]);
7374
setDropDown(false);
7475
}}
7576
key={cp.id}

src/components/Chat/ChatContent/Message/Message.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import useStore from '@store/store';
44
import Avatar from './Avatar';
55
import MessageContent from './MessageContent';
66

7-
import { Role } from '@type/chat';
7+
import { ContentInterface, Role } from '@type/chat';
88
import RoleSelector from './RoleSelector';
99

1010
// const backgroundStyle: { [role in Role]: string } = {
@@ -22,7 +22,7 @@ const Message = React.memo(
2222
sticky = false,
2323
}: {
2424
role: Role;
25-
content: string;
25+
content: ContentInterface[],
2626
messageIndex: number;
2727
sticky?: boolean;
2828
}) => {

src/components/Chat/ChatContent/Message/MessageContent.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import useStore from '@store/store';
33

44
import ContentView from './View/ContentView';
55
import EditView from './View/EditView';
6+
import { ContentInterface } from '@type/chat';
67

78
const MessageContent = ({
89
role,
@@ -11,7 +12,7 @@ const MessageContent = ({
1112
sticky = false,
1213
}: {
1314
role: string;
14-
content: string;
15+
content: ContentInterface[];
1516
messageIndex: number;
1617
sticky?: boolean;
1718
}) => {

0 commit comments

Comments
 (0)