diff --git a/app/api/auth.ts b/app/api/auth.ts index 1005c5ff..62fcd226 100644 --- a/app/api/auth.ts +++ b/app/api/auth.ts @@ -43,8 +43,7 @@ export function auth(req: NextRequest) { if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) { return { error: true, - needAccessCode: true, - msg: "Please go settings page and fill your access code.", + msg: !accessCode ? "empty access code" : "wrong access code", }; } @@ -58,7 +57,7 @@ export function auth(req: NextRequest) { console.log("[Auth] admin did not provide an api key"); return { error: true, - msg: "Empty Api Key", + msg: "admin did not provide an api key", }; } } else { diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index 4bdf9e05..99f36520 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -3,6 +3,8 @@ import { useAccessStore, useAppConfig, useChatStore } from "@/app/store"; import { ChatOptions, getHeaders, LLMApi, LLMUsage } from "../api"; import Locale from "../../locales"; +import { fetchEventSource } from "@microsoft/fetch-event-source"; +import { prettyObject } from "@/app/utils/format"; export class ChatGPTApi implements LLMApi { public ChatPath = "v1/chat/completions"; @@ -10,8 +12,10 @@ export class ChatGPTApi implements LLMApi { public SubsPath = "dashboard/billing/subscription"; path(path: string): string { - const openaiUrl = useAccessStore.getState().openaiUrl; - if (openaiUrl.endsWith("/")) openaiUrl.slice(0, openaiUrl.length - 1); + let openaiUrl = useAccessStore.getState().openaiUrl; + if (openaiUrl.endsWith("/")) { + openaiUrl = openaiUrl.slice(0, openaiUrl.length - 1); + } return [openaiUrl, path].join("/"); } @@ -69,40 +73,32 @@ export class ChatGPTApi implements LLMApi { options.onFinish(responseText); }; - const res = await fetch(chatPath, chatPayload); - clearTimeout(reqestTimeoutId); + controller.signal.onabort = finish; - if (res.status === 401) { - responseText += "\n\n" + Locale.Error.Unauthorized; - return finish(); - } + fetchEventSource(chatPath, { + ...chatPayload, + async onopen(res) { + clearTimeout(reqestTimeoutId); + if (res.status === 401) { + let extraInfo = { error: undefined }; + try { + extraInfo = await res.clone().json(); + } catch {} - if ( - !res.ok || - !res.headers.get("Content-Type")?.includes("stream") || - !res.body - ) { - return options.onError?.(new Error()); - } + responseText += "\n\n" + Locale.Error.Unauthorized; - const reader = res.body.getReader(); - const decoder = new TextDecoder("utf-8"); + if (extraInfo.error) { + responseText += "\n\n" + prettyObject(extraInfo); + } - while (true) { - const { done, value } = await reader.read(); - if (done) { - return finish(); - } - - const chunk = decoder.decode(value, { stream: true }); - const lines = chunk.split("data: "); - - for (const line of lines) { - const text = line.trim(); - if (line.startsWith("[DONE]")) { return finish(); } - if (text.length === 0) continue; + }, + onmessage(msg) { + if (msg.data === "[DONE]") { + return finish(); + } + const text = msg.data; try { const json = JSON.parse(text); const delta = json.choices[0].delta.content; @@ -111,10 +107,16 @@ export class ChatGPTApi implements LLMApi { options.onUpdate?.(responseText, delta); } } catch (e) { - console.error("[Request] parse error", text, chunk); + console.error("[Request] parse error", text, msg); } - } - } + }, + onclose() { + finish(); + }, + onerror(e) { + options.onError?.(e); + }, + }); } else { const res = await fetch(chatPath, chatPayload); clearTimeout(reqestTimeoutId); diff --git a/app/store/chat.ts b/app/store/chat.ts index 8fa32724..9bb9a803 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -249,7 +249,7 @@ export const useChatStore = create()( const systemInfo = createMessage({ role: "system", - content: `IMPRTANT: You are a virtual assistant powered by the ${ + content: `IMPORTANT: You are a virtual assistant powered by the ${ modelConfig.model } model, now time is ${new Date().toLocaleString()}}`, id: botMessage.id! + 1, @@ -296,9 +296,7 @@ export const useChatStore = create()( botMessage.content !== Locale.Error.Unauthorized && !isAborted ) { - botMessage.content += "\n\n" + Locale.Store.Error; - } else if (botMessage.content.length === 0) { - botMessage.content = prettyObject(error); + botMessage.content += "\n\n" + prettyObject(error); } botMessage.streaming = false; userMessage.isError = !isAborted; diff --git a/package.json b/package.json index 07ba977e..f9d3c3c7 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@hello-pangea/dnd": "^16.2.0", + "@microsoft/fetch-event-source": "^2.0.1", "@svgr/webpack": "^6.5.1", "@vercel/analytics": "^0.1.11", "emoji-picker-react": "^4.4.7", diff --git a/yarn.lock b/yarn.lock index 5240d7e7..e54a69e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1111,6 +1111,11 @@ dependencies: "@types/react" ">=16.0.0" +"@microsoft/fetch-event-source@^2.0.1": + version "2.0.1" + resolved "https://registry.npmmirror.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d" + integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA== + "@next/env@13.4.2": version "13.4.2" resolved "https://registry.npmmirror.com/@next/env/-/env-13.4.2.tgz#cf3ebfd523a33d8404c1216e02ac8d856a73170e"