Skip to content

Commit ccd4c97

Browse files
committed
feat: error handler & show error message
1 parent 1a0db57 commit ccd4c97

File tree

5 files changed

+71
-9
lines changed

5 files changed

+71
-9
lines changed

src/components/ErrorMessageItem.tsx

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { ErrorMessage } from '@/types'
2+
import IconRefresh from './icons/Refresh'
3+
4+
interface Props {
5+
data: ErrorMessage
6+
onRetry?: () => void
7+
}
8+
9+
export default ({ data, onRetry }: Props) => {
10+
return (
11+
<div class="my-4 px-4 py-3 border border-red/50 bg-red/10">
12+
{data.code && <div class="text-red mb-1">{data.code}</div>}
13+
<div class="text-red op-70 text-sm">{data.message}</div>
14+
{onRetry && (
15+
<div class="fie px-3 mb-2">
16+
<div onClick={onRetry} class="gpt-retry-btn border-red/50 text-red">
17+
<IconRefresh />
18+
<span>Regenerate</span>
19+
</div>
20+
</div>
21+
)}
22+
</div>
23+
)
24+
}

src/components/Generator.tsx

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import type { ChatMessage } from '@/types'
1+
import type { ChatMessage, ErrorMessage } from '@/types'
22
import { createSignal, Index, Show, onMount, onCleanup } from 'solid-js'
33
import IconClear from './icons/Clear'
44
import MessageItem from './MessageItem'
55
import SystemRoleSettings from './SystemRoleSettings'
6+
import ErrorMessageItem from './ErrorMessageItem'
67
import { generateSignature } from '@/utils/auth'
78
import { useThrottleFn } from 'solidjs-use'
89

@@ -11,6 +12,7 @@ export default () => {
1112
const [currentSystemRoleSettings, setCurrentSystemRoleSettings] = createSignal('')
1213
const [systemRoleEditing, setSystemRoleEditing] = createSignal(false)
1314
const [messageList, setMessageList] = createSignal<ChatMessage[]>([])
15+
const [currentError, setCurrentError] = createSignal<ErrorMessage>()
1416
const [currentAssistantMessage, setCurrentAssistantMessage] = createSignal('')
1517
const [loading, setLoading] = createSignal(false)
1618
const [controller, setController] = createSignal<AbortController>(null)
@@ -64,6 +66,7 @@ export default () => {
6466
const requestWithLatestMessage = async () => {
6567
setLoading(true)
6668
setCurrentAssistantMessage('')
69+
setCurrentError(null)
6770
const storagePassword = localStorage.getItem('pass')
6871
try {
6972
const controller = new AbortController()
@@ -90,7 +93,10 @@ export default () => {
9093
signal: controller.signal,
9194
})
9295
if (!response.ok) {
93-
throw new Error(response.statusText)
96+
const error = await response.json()
97+
console.error(error.error)
98+
setCurrentError(error.error)
99+
throw new Error('Request failed')
94100
}
95101
const data = response.body
96102
if (!data) {
@@ -160,8 +166,8 @@ export default () => {
160166
console.log(lastMessage)
161167
if (lastMessage.role === 'assistant') {
162168
setMessageList(messageList().slice(0, -1))
163-
requestWithLatestMessage()
164169
}
170+
requestWithLatestMessage()
165171
}
166172
}
167173

@@ -199,6 +205,7 @@ export default () => {
199205
message={currentAssistantMessage}
200206
/>
201207
)}
208+
{ currentError() && <ErrorMessageItem data={currentError()} onRetry={retryLastFetch} /> }
202209
<Show
203210
when={!loading()}
204211
fallback={() => (

src/pages/api/generate.ts

+25-5
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,25 @@ export const post: APIRoute = async (context) => {
1414
const body = await context.request.json()
1515
const { sign, time, messages, pass } = body
1616
if (!messages) {
17-
return new Response('No input text')
17+
return new Response(JSON.stringify({
18+
error: {
19+
message: 'No input text.',
20+
}
21+
}), { status: 400 })
1822
}
1923
if (sitePassword && sitePassword !== pass) {
20-
return new Response('Invalid password')
24+
return new Response(JSON.stringify({
25+
error: {
26+
message: 'Invalid password.',
27+
}
28+
}), { status: 401 })
2129
}
2230
if (import.meta.env.PROD && !await verifySignature({ t: time, m: messages?.[messages.length - 1]?.content || '', }, sign)) {
23-
return new Response('Invalid signature')
31+
return new Response(JSON.stringify({
32+
error: {
33+
message: 'Invalid signature.',
34+
}
35+
}), { status: 401 })
2436
}
2537
const initOptions = generatePayload(apiKey, messages)
2638
// #vercel-disable-blocks
@@ -30,7 +42,15 @@ export const post: APIRoute = async (context) => {
3042
// #vercel-end
3143

3244
// @ts-ignore
33-
const response = await fetch(`${baseUrl}/v1/chat/completions`, initOptions) as Response
45+
const response = await fetch(`${baseUrl}/v1/chat/completions`, initOptions).catch((err: Error) => {
46+
console.error(err)
47+
return new Response(JSON.stringify({
48+
error: {
49+
code: err.name,
50+
message: err.message,
51+
}
52+
}), { status: 500 })
53+
}) as Response
3454

35-
return new Response(parseOpenAIStream(response))
55+
return parseOpenAIStream(response) as Response
3656
}

src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ export interface ChatMessage {
22
role: 'system' | 'user' | 'assistant'
33
content: string
44
}
5+
6+
export interface ErrorMessage {
7+
code: string
8+
message: string
9+
}

src/utils/openAI.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ export const generatePayload = (apiKey: string, messages: ChatMessage[]): Reques
2020
export const parseOpenAIStream = (rawResponse: Response) => {
2121
const encoder = new TextEncoder()
2222
const decoder = new TextDecoder()
23+
if (!rawResponse.ok) {
24+
return new Response(rawResponse.body, {
25+
status: rawResponse.status,
26+
statusText: rawResponse.statusText,
27+
})
28+
}
2329

2430
const stream = new ReadableStream({
2531
async start(controller) {
@@ -57,5 +63,5 @@ export const parseOpenAIStream = (rawResponse: Response) => {
5763
},
5864
})
5965

60-
return stream
66+
return new Response(stream)
6167
}

0 commit comments

Comments
 (0)