in example-apps/chatbot-rag-app/frontend/src/store/provider.tsx [116:297]
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const actions = globalSlice.actions
export const thunkActions = {
search: (query: string) => {
return async function fetchSearch(dispatch, getState) {
if (getState().status === AppStatus.StreamingMessage) {
dispatch(thunkActions.abortRequest())
}
dispatch(actions.reset())
dispatch(thunkActions.chat(query))
}
},
askQuestion: (question: string) => {
return async function (dispatch, getState) {
const state = getState()
dispatch(
actions.addMessage({
conversation: {
isHuman: true,
content: question,
id: state.conversation.length + 1,
},
})
)
dispatch(thunkActions.chat(question))
}
},
chat: (question: string) => {
return async function fetchSearch(dispatch, getState) {
abortController = new AbortController()
const conversationId = getState().conversation.length + 1
dispatch(
actions.addMessage({
conversation: {
isHuman: false,
content: '',
id: conversationId,
},
})
)
dispatch(actions.setStatus({ status: AppStatus.StreamingMessage }))
let countRetiresError = 0
let message = ''
const sessionId = getState().sessionId
const sourcesMap: Map<
string,
{ name: string; url?: string; summary: string[] }
> = new Map()
await fetchEventSource(
`${API_HOST}/chat${sessionId ? `?session_id=${sessionId}` : ''}`,
{
method: 'POST',
openWhenHidden: true,
body: JSON.stringify({
question,
}),
headers: {
'Content-Type': 'application/json',
},
signal: abortController.signal,
async onmessage(event) {
if (event.event === 'FatalError') {
throw new FatalError(event.data)
}
if (event.data.startsWith(STREAMING_EVENTS.SESSION_ID)) {
const sessionId = event.data.split(' ')[1].trim()
dispatch(actions.setSessionId({ sessionId }))
} else if (event.data.startsWith(STREAMING_EVENTS.SOURCE)) {
const source = event.data.replace(
`${STREAMING_EVENTS.SOURCE} `,
''
)
try {
if (source) {
const parsedSource: {
name: string
page_content: string
url?: string
category?: string
updated_at?: string | null
} = JSON.parse(source.replaceAll('\n', ''))
if (parsedSource.page_content && parsedSource.name) {
dispatch(
actions.addSource({
source: {
name: parsedSource.name,
url: parsedSource.url,
summary: parsedSource.page_content,
icon: parsedSource.category,
updated_at: parsedSource.updated_at,
},
})
)
}
}
} catch (e) {
console.log('error', source, event.data)
console.error(e)
}
} else if (event.data === STREAMING_EVENTS.DONE) {
const sources = parseSources(message)
dispatch(
actions.setMessageSource({
id: conversationId,
sources,
})
)
dispatch(actions.setStatus({ status: AppStatus.Done }))
} else {
message += event.data
dispatch(
actions.updateMessage({
id: conversationId,
content: message.replace(/SOURCES:(.+)*/, ''),
})
)
}
},
async onopen(response) {
if (response.ok) {
return
} else if (
response.status >= 400 &&
response.status < 500 &&
response.status !== 429
) {
throw new FatalError()
} else {
throw new RetriableError()
}
},
onerror(err) {
if (err instanceof FatalError || countRetiresError > 3) {
dispatch(actions.setStatus({ status: AppStatus.Error }))
throw err
} else {
countRetiresError++
console.error(err)
}
},
}
)
}
},
abortRequest: () => {
return function (dispatch, getState) {
const messages = getState().conversation
const lastMessage = messages[getState().conversation.length - 1]
abortController?.abort()
abortController = null
if (!lastMessage.content) {
dispatch(
actions.removeMessage({
id: lastMessage.id,
})
)
}
dispatch(
actions.setStatus({
status: messages.length ? AppStatus.Done : AppStatus.Idle,
})
)
}
},
}