diff --git a/package-lock.json b/package-lock.json
index 1cc2afb..68aa793 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"openai": "^3.2.1",
"react": "18.2.0",
"react-dom": "18.2.0",
+ "react-hot-toast": "^2.4.0",
"react-i18next": "^12.2.0",
"react-markdown": "^8.0.5",
"react-syntax-highlighter": "^15.5.0",
@@ -3455,6 +3456,14 @@
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
"dev": true
},
+ "node_modules/goober": {
+ "version": "2.1.12",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.12.tgz",
+ "integrity": "sha512-yXHAvO08FU1JgTXX6Zn6sYCUFfB/OJSX8HHjDSgerZHZmFKAb08cykp5LBw5QnmyMcZyPRMqkdyHUSSzge788Q==",
+ "peerDependencies": {
+ "csstype": "^3.0.10"
+ }
+ },
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -6394,6 +6403,21 @@
"react": "^18.2.0"
}
},
+ "node_modules/react-hot-toast": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.0.tgz",
+ "integrity": "sha512-qnnVbXropKuwUpriVVosgo8QrB+IaPJCpL8oBI6Ov84uvHZ5QQcTp2qg6ku2wNfgJl6rlQXJIQU5q+5lmPOutA==",
+ "dependencies": {
+ "goober": "^2.1.10"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=16",
+ "react-dom": ">=16"
+ }
+ },
"node_modules/react-i18next": {
"version": "12.2.0",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.2.0.tgz",
@@ -10613,6 +10637,12 @@
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
"dev": true
},
+ "goober": {
+ "version": "2.1.12",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.12.tgz",
+ "integrity": "sha512-yXHAvO08FU1JgTXX6Zn6sYCUFfB/OJSX8HHjDSgerZHZmFKAb08cykp5LBw5QnmyMcZyPRMqkdyHUSSzge788Q==",
+ "requires": {}
+ },
"gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -12517,6 +12547,14 @@
"scheduler": "^0.23.0"
}
},
+ "react-hot-toast": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.0.tgz",
+ "integrity": "sha512-qnnVbXropKuwUpriVVosgo8QrB+IaPJCpL8oBI6Ov84uvHZ5QQcTp2qg6ku2wNfgJl6rlQXJIQU5q+5lmPOutA==",
+ "requires": {
+ "goober": "^2.1.10"
+ }
+ },
"react-i18next": {
"version": "12.2.0",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.2.0.tgz",
diff --git a/package.json b/package.json
index ed70cb1..029753c 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"openai": "^3.2.1",
"react": "18.2.0",
"react-dom": "18.2.0",
+ "react-hot-toast": "^2.4.0",
"react-i18next": "^12.2.0",
"react-markdown": "^8.0.5",
"react-syntax-highlighter": "^15.5.0",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 388cbb5..1cf463f 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -2,12 +2,14 @@ import '@/styles/globals.css';
import { appWithTranslation } from 'next-i18next';
import type { AppProps } from 'next/app';
import { Inter } from 'next/font/google';
+import { Toaster } from 'react-hot-toast';
const inter = Inter({ subsets: ['latin'] });
function App({ Component, pageProps }: AppProps<{}>) {
return (
+
);
diff --git a/pages/api/chat.ts b/pages/api/chat.ts
index 09c0aaf..4083fd9 100644
--- a/pages/api/chat.ts
+++ b/pages/api/chat.ts
@@ -1,6 +1,6 @@
import { ChatBody, Message } from '@/types/chat';
import { DEFAULT_SYSTEM_PROMPT } from '@/utils/app/const';
-import { OpenAIStream } from '@/utils/server';
+import { OpenAIError, OpenAIStream } from '@/utils/server';
import tiktokenModel from '@dqbd/tiktoken/encoders/cl100k_base.json';
import { init, Tiktoken } from '@dqbd/tiktoken/lite/init';
// @ts-expect-error
@@ -49,7 +49,11 @@ const handler = async (req: Request): Promise => {
return new Response(stream);
} catch (error) {
console.error(error);
- return new Response('Error', { status: 500 });
+ if (error instanceof OpenAIError) {
+ return new Response('Error', { status: 500, statusText: error.message });
+ } else {
+ return new Response('Error', { status: 500 });
+ }
}
};
diff --git a/pages/index.tsx b/pages/index.tsx
index ea205b5..9d906d3 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -34,6 +34,7 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import Head from 'next/head';
import { useEffect, useRef, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
+import toast from 'react-hot-toast';
interface HomeProps {
serverSideApiKeyIsSet: boolean;
@@ -120,6 +121,7 @@ const Home: React.FC = ({
if (!response.ok) {
setLoading(false);
setMessageIsStreaming(false);
+ toast.error(response.statusText);
return;
}
diff --git a/utils/server/index.ts b/utils/server/index.ts
index 9366af8..8ab473d 100644
--- a/utils/server/index.ts
+++ b/utils/server/index.ts
@@ -7,6 +7,20 @@ import {
} from 'eventsource-parser';
import { OPENAI_API_HOST } from '../app/const';
+export class OpenAIError extends Error {
+ type: string;
+ param: string;
+ code: string;
+
+ constructor(message: string, type: string, param: string, code: string) {
+ super(message);
+ this.name = 'OpenAIError';
+ this.type = type;
+ this.param = param;
+ this.code = code;
+ }
+}
+
export const OpenAIStream = async (
model: OpenAIModel,
systemPrompt: string,
@@ -41,9 +55,21 @@ export const OpenAIStream = async (
const decoder = new TextDecoder();
if (res.status !== 200) {
- const statusText = res.statusText;
- const result = await res.body?.getReader().read();
- throw new Error(`OpenAI API returned an error: ${decoder.decode(result?.value) || statusText}`);
+ const result = await res.json();
+ if (result.error) {
+ throw new OpenAIError(
+ result.error.message,
+ result.error.type,
+ result.error.param,
+ result.error.code,
+ );
+ } else {
+ throw new Error(
+ `OpenAI API returned an error: ${
+ decoder.decode(result?.value) || result.statusText
+ }`,
+ );
+ }
}
const stream = new ReadableStream({