Handle Streaming
Example of making a request to the Copilot API and handling the streaming of the messages in a React frontend.
// Handles incoming messages from the server
const handleIncomingMessage = ({ prevMessages, message }: { prevMessages: UserChatMessage[]; message: UserChatMessage }) => {
const existingIndex = prevMessages.findIndex((m) => m.id === message.id)
if (existingIndex === -1) {
// If the message is not in the list, add it
return [...prevMessages, message]
} else {
// Clone the array for immutability
const updatedMessages = [...prevMessages]
updatedMessages[existingIndex] = message
return updatedMessages
}
}
const handleUserQuery = async ({
query,
history,
setLoading,
setMessages,
setChatTitle,
setChatSamplePrompts,
setAbortController
}: {
query: string
history?: UserChatMessage[]
setLoading: (loading: boolean) => void
setMessages: React.Dispatch<React.SetStateAction<UserChatMessage[]>>
setChatTitle: React.Dispatch<React.SetStateAction<string>>
setChatSamplePrompts: React.Dispatch<React.SetStateAction<string[]>>
setAbortController: (value: AbortController | null) => void
}) => {
try {
setLoading(true)
const res = await fetch('https://api.finchat.io/query', {
method: 'POST',
headers: { Accept: 'text/event-stream', 'Authorization': `Bearer ${process.env.FINCHAT_ENTERPRISE_API_KEY}` },
body: JSON.stringify({
query,
history,
inlineSourcing: true,
generateTitle: !history || !history.length,
generateSamplePrompts: true
})
})
if (!res.ok) throw new Error('Failed to fetch')
if (!res.body) throw new Error('No body')
const abortController = new AbortController()
setAbortController(abortController)
const reader = res.body.getReader()
const decoder = new TextDecoder()
let newMessages: UserChatMessage[] = [];
let done = false
let accumulatedChunks = '';
while (!done && !abortController.signal.aborted) {
const { value, done: doneReading } = await reader.read()
done = doneReading
const chunkValue = decoder.decode(value, { stream: true })
accumulatedChunks += chunkValue // Accumulate chunks
let messageBoundaryIndex
while ((messageBoundaryIndex = accumulatedChunks.indexOf('\n\n')) !== -1) {
const completeMessage = accumulatedChunks.substring(0, messageBoundaryIndex)
accumulatedChunks = accumulatedChunks.substring(messageBoundaryIndex + 2) // Remove the processed message from the accumulator
if (!completeMessage) continue
try {
const messageJsonString = JSON.parse(
completeMessage
.split('\n')
.map((line) => line.substring(6))
.join('\n')
) as (UserChatMessage | GenerateTitleResponse | GenerateSamplePromptsResponse)
if (messageJsonString.type === 'Title') {
setChatTitle(messageJsonString.title)
} else if (messageJsonString.type === 'Sample Prompts') {
setChatSamplePrompts(messageJsonString.prompts)
} else if (messageJsonString.type === 'Message') {
// if the previous message id is the same as the current message id, then the bot is typing
if (newMessages.length > 0 && newMessages[newMessages.length - 1].id === messageJsonString.id && !messageJsonString.hideContent) {
setLoading(false)
}
setMessages((prevMessages: UserChatMessage[]) => handleIncomingMessage({ prevMessages, message: messageJsonString }))
newMessages = handleIncomingMessage({ prevMessages: newMessages, message: messageJsonString })
}
} catch (e) {
console.error('Failed to parse a chunk:', completeMessage)
}
}
}
} catch (e) {
console.error(e)
} finally {
setLoading(false)
}
}