diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index 585fbb6..b1698d0 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -259,18 +259,26 @@ const ConversationView = () => { const reader = data.getReader(); const decoder = new TextDecoder("utf-8"); let done = false; - while (!done) { - const { value, done: readerDone } = await reader.read(); - if (value) { - const char = decoder.decode(value); - if (char) { - assistantMessage.content = assistantMessage.content + char; - messageStore.updateMessage(assistantMessage.id, { - content: assistantMessage.content, - }); + try { + while (!done) { + const { value, done: readerDone } = await reader.read(); + if (value) { + const char = decoder.decode(value); + if (char) { + assistantMessage.content = assistantMessage.content + char; + messageStore.updateMessage(assistantMessage.id, { + content: assistantMessage.content, + }); + } } + done = readerDone; } - done = readerDone; + } catch (error) { + messageStore.updateMessage(assistantMessage.id, { + content: assistantMessage.content || "Failed to receive response. Please check your API endpoint configuration.", + status: "FAILED", + }); + return; } messageStore.updateMessage(assistantMessage.id, { status: "DONE", diff --git a/src/pages/api/chat.ts b/src/pages/api/chat.ts index 4919e95..9e53d19 100644 --- a/src/pages/api/chat.ts +++ b/src/pages/api/chat.ts @@ -140,8 +140,10 @@ const handler = async (req: NextRequest) => { try { const json = JSON.parse(data); const text = json.choices[0].delta?.content; - const queue = encoder.encode(text); - controller.enqueue(queue); + if (text) { + const queue = encoder.encode(text); + controller.enqueue(queue); + } } catch (e) { controller.error(e); } @@ -151,6 +153,14 @@ const handler = async (req: NextRequest) => { for await (const chunk of remoteRes.body as any) { parser.feed(decoder.decode(chunk)); } + // Ensure the stream is closed after all upstream chunks are consumed. + // Some providers (e.g. Ollama native API) may not send a [DONE] sentinel, + // which would leave the ReadableStream open and the client hanging indefinitely. + try { + controller.close(); + } catch { + // Already closed by the [DONE] handler — ignore. + } }, });