feat: 侧边栏切换取消上次请求
This commit is contained in:
parent
d2ae2c4f54
commit
4458e744cc
|
@ -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',
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue