add react-hot-toast and surface OpenAI API errors to users (#328)
This commit is contained in:
parent
23ad285a4b
commit
b7b6bbaaca
|
@ -17,6 +17,7 @@
|
||||||
"openai": "^3.2.1",
|
"openai": "^3.2.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"react-hot-toast": "^2.4.0",
|
||||||
"react-i18next": "^12.2.0",
|
"react-i18next": "^12.2.0",
|
||||||
"react-markdown": "^8.0.5",
|
"react-markdown": "^8.0.5",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
|
@ -3455,6 +3456,14 @@
|
||||||
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
|
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/gopd": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||||
|
@ -6394,6 +6403,21 @@
|
||||||
"react": "^18.2.0"
|
"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": {
|
"node_modules/react-i18next": {
|
||||||
"version": "12.2.0",
|
"version": "12.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.2.0.tgz",
|
||||||
|
@ -10613,6 +10637,12 @@
|
||||||
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
|
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"goober": {
|
||||||
|
"version": "2.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.12.tgz",
|
||||||
|
"integrity": "sha512-yXHAvO08FU1JgTXX6Zn6sYCUFfB/OJSX8HHjDSgerZHZmFKAb08cykp5LBw5QnmyMcZyPRMqkdyHUSSzge788Q==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"gopd": {
|
"gopd": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||||
|
@ -12517,6 +12547,14 @@
|
||||||
"scheduler": "^0.23.0"
|
"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": {
|
"react-i18next": {
|
||||||
"version": "12.2.0",
|
"version": "12.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.2.0.tgz",
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"openai": "^3.2.1",
|
"openai": "^3.2.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"react-hot-toast": "^2.4.0",
|
||||||
"react-i18next": "^12.2.0",
|
"react-i18next": "^12.2.0",
|
||||||
"react-markdown": "^8.0.5",
|
"react-markdown": "^8.0.5",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
|
|
|
@ -2,12 +2,14 @@ import '@/styles/globals.css';
|
||||||
import { appWithTranslation } from 'next-i18next';
|
import { appWithTranslation } from 'next-i18next';
|
||||||
import type { AppProps } from 'next/app';
|
import type { AppProps } from 'next/app';
|
||||||
import { Inter } from 'next/font/google';
|
import { Inter } from 'next/font/google';
|
||||||
|
import { Toaster } from 'react-hot-toast';
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] });
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
|
|
||||||
function App({ Component, pageProps }: AppProps<{}>) {
|
function App({ Component, pageProps }: AppProps<{}>) {
|
||||||
return (
|
return (
|
||||||
<main className={inter.className}>
|
<main className={inter.className}>
|
||||||
|
<Toaster />
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ChatBody, Message } from '@/types/chat';
|
import { ChatBody, Message } from '@/types/chat';
|
||||||
import { DEFAULT_SYSTEM_PROMPT } from '@/utils/app/const';
|
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 tiktokenModel from '@dqbd/tiktoken/encoders/cl100k_base.json';
|
||||||
import { init, Tiktoken } from '@dqbd/tiktoken/lite/init';
|
import { init, Tiktoken } from '@dqbd/tiktoken/lite/init';
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
|
@ -49,8 +49,12 @@ const handler = async (req: Request): Promise<Response> => {
|
||||||
return new Response(stream);
|
return new Response(stream);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
if (error instanceof OpenAIError) {
|
||||||
|
return new Response('Error', { status: 500, statusText: error.message });
|
||||||
|
} else {
|
||||||
return new Response('Error', { status: 500 });
|
return new Response('Error', { status: 500 });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handler;
|
export default handler;
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
interface HomeProps {
|
interface HomeProps {
|
||||||
serverSideApiKeyIsSet: boolean;
|
serverSideApiKeyIsSet: boolean;
|
||||||
|
@ -120,6 +121,7 @@ const Home: React.FC<HomeProps> = ({
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setMessageIsStreaming(false);
|
setMessageIsStreaming(false);
|
||||||
|
toast.error(response.statusText);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,20 @@ import {
|
||||||
} from 'eventsource-parser';
|
} from 'eventsource-parser';
|
||||||
import { OPENAI_API_HOST } from '../app/const';
|
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 (
|
export const OpenAIStream = async (
|
||||||
model: OpenAIModel,
|
model: OpenAIModel,
|
||||||
systemPrompt: string,
|
systemPrompt: string,
|
||||||
|
@ -41,9 +55,21 @@ export const OpenAIStream = async (
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
const statusText = res.statusText;
|
const result = await res.json();
|
||||||
const result = await res.body?.getReader().read();
|
if (result.error) {
|
||||||
throw new Error(`OpenAI API returned an error: ${decoder.decode(result?.value) || statusText}`);
|
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({
|
const stream = new ReadableStream({
|
||||||
|
|
Loading…
Reference in New Issue