feat: 侧边栏切换取消上次请求

This commit is contained in:
ChenZhaoYu 2023-02-14 16:57:11 +08:00
parent d2ae2c4f54
commit 4458e744cc
4 changed files with 40 additions and 24 deletions

View File

@ -1,17 +1,16 @@
import type { GenericAbortSignal } from 'axios'
import { post } from '@/utils/request' import { post } from '@/utils/request'
export const controller = new AbortController()
export function fetchChatAPI<T = any>( export function fetchChatAPI<T = any>(
prompt: string, prompt: string,
options?: { conversationId?: string; parentMessageId?: string }, options?: { conversationId?: string; parentMessageId?: string },
signal?: GenericAbortSignal,
) { ) {
return post<T>({ return post<T>({
url: '/chat', url: '/chat',
data: { prompt, options }, data: { prompt, options },
}) signal,
}
export function clearConversations<T = any>() {
return post<T>({
url: '/clear',
}) })
} }

View File

@ -1,5 +1,5 @@
<script setup lang='ts'> <script setup lang='ts'>
import { computed, nextTick, ref, watch } from 'vue' import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { NButton, NInput, useMessage } from 'naive-ui' import { NButton, NInput, useMessage } from 'naive-ui'
import { Message } from './components' import { Message } from './components'
import { Layout } from './layout' import { Layout } from './layout'
@ -9,13 +9,15 @@ import { HoverButton, SvgIcon } from '@/components/common'
import { useHistoryStore } from '@/store' import { useHistoryStore } from '@/store'
import { isNumber } from '@/utils/is' import { isNumber } from '@/utils/is'
let controller = new AbortController()
const ms = useMessage() const ms = useMessage()
const historyStore = useHistoryStore() const historyStore = useHistoryStore()
const scrollRef = ref<HTMLDivElement>() const scrollRef = ref<HTMLDivElement>()
const { addChat, clearChat } = useChat() const { addChat, clearChat: handleClear } = useChat()
const prompt = ref('') const prompt = ref('')
const loading = ref(false) const loading = ref(false)
@ -47,10 +49,11 @@ async function handleSubmit() {
try { try {
loading.value = true loading.value = true
const { data } = await fetchChatAPI(message, options) const { data } = await fetchChatAPI(message, options, controller.signal)
addMessage(data?.text ?? '', { options: { conversationId: data.conversationId, parentMessageId: data.id } }) addMessage(data?.text ?? '', { options: { conversationId: data.conversationId, parentMessageId: data.id } })
} }
catch (error: any) { catch (error: any) {
if (error.message !== 'cancelled')
addMessage(`Error: ${error.message ?? 'Request failed, please try again later.'}`, { error: true }) addMessage(`Error: ${error.message ?? 'Request failed, please try again later.'}`, { error: true })
} }
finally { finally {
@ -69,22 +72,32 @@ function addMessage(
uuid?: number | null, uuid?: number | null,
) { ) {
addChat(message, args, uuid) addChat(message, args, uuid)
scrollToBottom()
}
function scrollToBottom() {
nextTick(() => scrollRef.value && (scrollRef.value.scrollTop = scrollRef.value.scrollHeight)) nextTick(() => scrollRef.value && (scrollRef.value.scrollTop = scrollRef.value.scrollHeight))
} }
function handleClear() { function handleCancel() {
clearChat() //
controller.abort()
controller = new AbortController()
loading.value = false
} }
onMounted(() => {
scrollToBottom()
})
watch( watch(
currentActive, currentActive,
(active: number | null) => { (active) => {
if (isNumber(active)) { if (isNumber(active)) {
loading.value = false handleCancel()
nextTick(() => scrollRef.value && (scrollRef.value.scrollTop = scrollRef.value.scrollHeight)) scrollToBottom()
} }
}, },
{ immediate: true },
) )
</script> </script>
@ -109,7 +122,7 @@ watch(
</span> </span>
</HoverButton> </HoverButton>
<NInput v-model:value="prompt" placeholder="Type a message..." @keypress="handleEnter" /> <NInput v-model:value="prompt" placeholder="Type a message..." @keypress="handleEnter" />
<NButton type="primary" :loading="loading" @click="handleSubmit"> <NButton type="primary" :loading="loading" @click="handleCancel">
<template #icon> <template #icon>
<SvgIcon icon="ri:send-plane-fill" /> <SvgIcon icon="ri:send-plane-fill" />
</template> </template>

View File

@ -1,7 +1,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { HistoryState } from './helper' import type { HistoryState } from './helper'
import { getLocalHistory, setLocalHistory } from './helper' import { getLocalHistory, setLocalHistory } from './helper'
export const useHistoryStore = defineStore('history-store', { export const useHistoryStore = defineStore('history-store', {
state: (): HistoryState => getLocalHistory(), state: (): HistoryState => getLocalHistory(),
getters: { getters: {
@ -65,6 +64,8 @@ export const useHistoryStore = defineStore('history-store', {
}, },
chooseHistory(index: number) { chooseHistory(index: number) {
if (this.active === index)
return
this.active = index this.active = index
setLocalHistory(this.$state) setLocalHistory(this.$state)
}, },

View File

@ -1,4 +1,4 @@
import type { AxiosResponse } from 'axios' import type { AxiosResponse, GenericAbortSignal } from 'axios'
import request from './axios' import request from './axios'
export interface HttpOption { export interface HttpOption {
@ -6,6 +6,7 @@ export interface HttpOption {
data?: any data?: any
method?: string method?: string
headers?: any headers?: any
signal?: GenericAbortSignal
beforeRequest?: () => void beforeRequest?: () => void
afterRequest?: () => void afterRequest?: () => void
} }
@ -20,7 +21,7 @@ export interface Response<T = any> {
status: string status: string
} }
function http<T = any>({ url, data, method, headers, beforeRequest, afterRequest }: HttpOption) { function http<T = any>({ url, data, method, headers, signal, beforeRequest, afterRequest }: HttpOption) {
const successHandler = (res: AxiosResponse<Response<T>>) => { const successHandler = (res: AxiosResponse<Response<T>>) => {
if (res.data.status === 'Success') if (res.data.status === 'Success')
return res.data return res.data
@ -40,30 +41,32 @@ function http<T = any>({ url, data, method, headers, beforeRequest, afterRequest
const params = Object.assign(typeof data === 'function' ? data() : data ?? {}, {}) const params = Object.assign(typeof data === 'function' ? data() : data ?? {}, {})
return method === 'GET' return method === 'GET'
? request.get(url, { params }).then(successHandler, failHandler) ? request.get(url, { params, signal }).then(successHandler, failHandler)
: request.post(url, params, { headers }).then(successHandler, failHandler) : request.post(url, params, { headers, signal }).then(successHandler, failHandler)
} }
export function get<T = any>( export function get<T = any>(
{ url, data, method = 'GET', beforeRequest, afterRequest }: HttpOption, { url, data, method = 'GET', signal, beforeRequest, afterRequest }: HttpOption,
): Promise<Response<T>> { ): Promise<Response<T>> {
return http<T>({ return http<T>({
url, url,
method, method,
data, data,
signal,
beforeRequest, beforeRequest,
afterRequest, afterRequest,
}) })
} }
export function post<T = any>( export function post<T = any>(
{ url, data, method = 'POST', headers, beforeRequest, afterRequest }: HttpOption, { url, data, method = 'POST', headers, signal, beforeRequest, afterRequest }: HttpOption,
): Promise<Response<T>> { ): Promise<Response<T>> {
return http<T>({ return http<T>({
url, url,
method, method,
data, data,
headers, headers,
signal,
beforeRequest, beforeRequest,
afterRequest, afterRequest,
}) })