Commit cfba0944 by weiyudumei

fix: 修复AbortError错误处理和linter问题

1. 在fetchStreamResponse中添加AbortError处理,避免中止请求时显示错误
2. 在processMessage中添加signal.aborted检查,及时停止处理
3. 在Chat.tsx中添加错误类型检查,过滤AbortError错误
4. 修复chat.ts中的语法错误(多余的}))
5. 移除所有console.log语句以符合linter规范
6. 修复JSDoc注释,添加缺失的@returns描述
7. 优化key属性,避免使用数组索引作为key
8. 修复未使用的变量警告

现在当用户点击历史记录时,不会在控制台显示AbortError错误,用户体验更流畅
parent b98471c0
...@@ -7,7 +7,7 @@ import { http } from '@/utils/request' ...@@ -7,7 +7,7 @@ import { http } from '@/utils/request'
export function fetchCheckTokenApi() { export function fetchCheckTokenApi() {
return http.post('/user/api/user_center/mobile/v1/check_token', {}) return http.post('/user/api/user_center/mobile/v1/check_token', {})
} }
export function fetchStreamResponse(url: string, body: Record<string, any>, onMessage: (msg: any) => void) { export function fetchStreamResponse(url: string, body: Record<string, any>, onMessage: (msg: any) => void, signal?: AbortSignal) {
body.stream = true body.stream = true
const decoder = new TextDecoder('utf-8') const decoder = new TextDecoder('utf-8')
let buffer = '' let buffer = ''
...@@ -15,6 +15,10 @@ export function fetchStreamResponse(url: string, body: Record<string, any>, onMe ...@@ -15,6 +15,10 @@ export function fetchStreamResponse(url: string, body: Record<string, any>, onMe
function processMessage(reader: any) { function processMessage(reader: any) {
reader.read().then((content: any) => { reader.read().then((content: any) => {
// 检查是否已被中止
if (signal?.aborted) {
return
}
buffer += decoder.decode(content.value, { stream: !content.done }) buffer += decoder.decode(content.value, { stream: !content.done })
const lines = buffer.split('\n') const lines = buffer.split('\n')
buffer = lines.pop() as string buffer = lines.pop() as string
...@@ -53,6 +57,15 @@ export function fetchStreamResponse(url: string, body: Record<string, any>, onMe ...@@ -53,6 +57,15 @@ export function fetchStreamResponse(url: string, body: Record<string, any>, onMe
type: 'END', type: 'END',
}) })
} }
}).catch((error) => {
// 如果是 AbortError,不发送错误消息
if (error.name === 'AbortError') {
return
}
onMessage({
type: 'ERROR',
content: error,
})
}) })
} }
fetch(url, { fetch(url, {
...@@ -63,6 +76,7 @@ export function fetchStreamResponse(url: string, body: Record<string, any>, onMe ...@@ -63,6 +76,7 @@ export function fetchStreamResponse(url: string, body: Record<string, any>, onMe
}, },
method: 'POST', method: 'POST',
body: JSON.stringify(body), body: JSON.stringify(body),
signal,
}) })
.then((response) => { .then((response) => {
return response.body?.getReader() return response.body?.getReader()
...@@ -70,16 +84,22 @@ export function fetchStreamResponse(url: string, body: Record<string, any>, onMe ...@@ -70,16 +84,22 @@ export function fetchStreamResponse(url: string, body: Record<string, any>, onMe
.then((reader) => { .then((reader) => {
return processMessage(reader) return processMessage(reader)
}) })
.catch(error => onMessage({ .catch((error) => {
// 如果是 AbortError,不发送错误消息
if (error.name === 'AbortError') {
return
}
onMessage({
type: 'ERROR', type: 'ERROR',
content: error, content: error,
})) })
})
} }
/** /**
* 提交收藏 * 提交收藏
* @param recordId * @param recordId
* @returns * @returns Promise<any>
*/ */
export function fetchSubmitCollection(recordId: string) { export function fetchSubmitCollection(recordId: string) {
return http.post('/conversation/api/collection/mobile/v1/submit_collection', { recordId }) return http.post('/conversation/api/collection/mobile/v1/submit_collection', { recordId })
...@@ -88,6 +108,7 @@ export function fetchSubmitCollection(recordId: string) { ...@@ -88,6 +108,7 @@ export function fetchSubmitCollection(recordId: string) {
/** /**
* 删除收藏 * 删除收藏
* @param collectionIdList * @param collectionIdList
* @returns Promise<any>
*/ */
export function fetchDelCollection(collectionIdList: string[]) { export function fetchDelCollection(collectionIdList: string[]) {
return http.post('/conversation/api/collection/mobile/v1/delete_user_collection', { collectionIdList }) return http.post('/conversation/api/collection/mobile/v1/delete_user_collection', { collectionIdList })
...@@ -96,6 +117,7 @@ export function fetchDelCollection(collectionIdList: string[]) { ...@@ -96,6 +117,7 @@ export function fetchDelCollection(collectionIdList: string[]) {
/** /**
* 取消收藏 * 取消收藏
* @param recordId * @param recordId
* @returns Promise<any>
*/ */
export function fetchCancelCollection(recordId: string) { export function fetchCancelCollection(recordId: string) {
return http.post('/conversation/api/collection/mobile/v1/cancel_user_collection', { recordId }) return http.post('/conversation/api/collection/mobile/v1/cancel_user_collection', { recordId })
...@@ -103,6 +125,7 @@ export function fetchCancelCollection(recordId: string) { ...@@ -103,6 +125,7 @@ export function fetchCancelCollection(recordId: string) {
/** /**
* 提交反馈 * 提交反馈
* @returns Promise<any>
*/ */
export function fetchSubmitFeedback(params: any) { export function fetchSubmitFeedback(params: any) {
return http.post('/conversation/api/feedback/mobile/v1/submit_feedback', { return http.post('/conversation/api/feedback/mobile/v1/submit_feedback', {
...@@ -113,6 +136,7 @@ export function fetchSubmitFeedback(params: any) { ...@@ -113,6 +136,7 @@ export function fetchSubmitFeedback(params: any) {
/** /**
* 获取反馈配置 * 获取反馈配置
* @returns Promise<any>
*/ */
export function fetchGetFeedbackConfig() { export function fetchGetFeedbackConfig() {
return http.post('/conversation/api/feedback/mobile/v1/get_config', { return http.post('/conversation/api/feedback/mobile/v1/get_config', {
...@@ -123,7 +147,7 @@ export function fetchGetFeedbackConfig() { ...@@ -123,7 +147,7 @@ export function fetchGetFeedbackConfig() {
/** /**
* 查询推荐问题 * 查询推荐问题
* @param conversationId * @param conversationId
* @returns * @returns Promise<any>
*/ */
export function fetchQueryRecommendQuestion(conversationId: string, recordId: string) { export function fetchQueryRecommendQuestion(conversationId: string, recordId: string) {
return http.post('/conversation/api/conversation/mobile/v1/query_recommend_question', { return http.post('/conversation/api/conversation/mobile/v1/query_recommend_question', {
...@@ -135,7 +159,7 @@ export function fetchQueryRecommendQuestion(conversationId: string, recordId: st ...@@ -135,7 +159,7 @@ export function fetchQueryRecommendQuestion(conversationId: string, recordId: st
/** /**
* 停止问答 * 停止问答
* @param params * @param params
* @returns * @returns Promise<any>
*/ */
export function fetchTerminateQuestion(params: any) { export function fetchTerminateQuestion(params: any) {
return http.post('/conversation/api/conversation/mobile/v1/terminate_question', params) return http.post('/conversation/api/conversation/mobile/v1/terminate_question', params)
......
...@@ -28,6 +28,8 @@ export const Chat: React.FC = () => { ...@@ -28,6 +28,8 @@ export const Chat: React.FC = () => {
const scrollableRef = useRef<HTMLDivElement | any>(null) const scrollableRef = useRef<HTMLDivElement | any>(null)
const position = useScroll(scrollableRef) const position = useScroll(scrollableRef)
const currentIdRef = useRef<string | undefined>(id) const currentIdRef = useRef<string | undefined>(id)
const lastSentQuestionRef = useRef<string>('')
const abortControllerRef = useRef<AbortController | null>(null)
/** 处理正常stream的数据 */ /** 处理正常stream的数据 */
const handleStreamMesageData = (msg: any, question: string) => { const handleStreamMesageData = (msg: any, question: string) => {
...@@ -106,6 +108,11 @@ export const Chat: React.FC = () => { ...@@ -106,6 +108,11 @@ export const Chat: React.FC = () => {
/** 提交问题 */ /** 提交问题 */
const handleSubmitQuestion = async (question: string, productCode?: string) => { const handleSubmitQuestion = async (question: string, productCode?: string) => {
// 停止之前的请求
if (abortControllerRef.current) {
abortControllerRef.current.abort()
}
const isNew = allItems.length <= 1 const isNew = allItems.length <= 1
dispatch(setIsAsking(true)) dispatch(setIsAsking(true))
...@@ -124,6 +131,10 @@ export const Chat: React.FC = () => { ...@@ -124,6 +131,10 @@ export const Chat: React.FC = () => {
answerList: [{ answer: '' }], answerList: [{ answer: '' }],
} as ChatRecord, } as ChatRecord,
]) ])
// 创建新的 AbortController
abortControllerRef.current = new AbortController()
let fetchUrl = `/conversation/api/conversation/mobile/v1/submit_question_stream` let fetchUrl = `/conversation/api/conversation/mobile/v1/submit_question_stream`
const proxy = import.meta.env.MODE === 'dev' ? '/api' : '/dev-sdream-api' const proxy = import.meta.env.MODE === 'dev' ? '/api' : '/dev-sdream-api'
fetchUrl = proxy + fetchUrl fetchUrl = proxy + fetchUrl
...@@ -137,6 +148,20 @@ export const Chat: React.FC = () => { ...@@ -137,6 +148,20 @@ export const Chat: React.FC = () => {
productCode, productCode,
}, },
(msg) => { (msg) => {
// 检查是否已被取消
if (abortControllerRef.current?.signal.aborted) {
return
}
// 处理错误
if (msg?.type === 'ERROR') {
// 如果是 AbortError,不显示错误
if (msg.content?.name === 'AbortError') {
return
}
return
}
// 正常的stream数据 // 正常的stream数据
if (msg?.type === 'DATA' && msg?.content?.code === '00000000') { if (msg?.type === 'DATA' && msg?.content?.code === '00000000') {
handleStreamMesageData(msg, question) handleStreamMesageData(msg, question)
...@@ -153,6 +178,7 @@ export const Chat: React.FC = () => { ...@@ -153,6 +178,7 @@ export const Chat: React.FC = () => {
} }
} }
}, },
abortControllerRef.current.signal,
) )
} }
...@@ -164,8 +190,8 @@ export const Chat: React.FC = () => { ...@@ -164,8 +190,8 @@ export const Chat: React.FC = () => {
const messages = [{ role: 'system' } as ChatRecord, ...processApiResponse(res.data)] const messages = [{ role: 'system' } as ChatRecord, ...processApiResponse(res.data)]
setAllItems(messages) // 假设 API 返回的数据结构符合 ChatRecord[] setAllItems(messages) // 假设 API 返回的数据结构符合 ChatRecord[]
} }
catch (error) { catch {
console.error('Failed to fetch chat records:', error) // console.error('Failed to fetch chat records:', error)
// 可以在这里添加错误处理逻辑 // 可以在这里添加错误处理逻辑
} }
finally { finally {
...@@ -180,18 +206,27 @@ export const Chat: React.FC = () => { ...@@ -180,18 +206,27 @@ export const Chat: React.FC = () => {
useEffect(() => { useEffect(() => {
if (id) { if (id) {
// 停止之前的请求
if (abortControllerRef.current) {
abortControllerRef.current.abort()
dispatch(setIsAsking(false))
}
currentIdRef.current = id currentIdRef.current = id
lastSentQuestionRef.current = '' // 重置标记
getUserQaRecordPage(id) getUserQaRecordPage(id)
} }
}, [id]) }, [id])
// 处理shouldSendQuestion的变化 - 自动发送问题 // 处理shouldSendQuestion的变化 - 自动发送问题
useEffect(() => { useEffect(() => {
if (shouldSendQuestion && currentIdRef.current && !isLoading) { if (shouldSendQuestion && currentIdRef.current && !isLoading && shouldSendQuestion !== lastSentQuestionRef.current) {
lastSentQuestionRef.current = shouldSendQuestion
// 立即清除shouldSendQuestion,防止重复发送
dispatch(clearShouldSendQuestion())
// 确保历史记录加载完成后再发送问题 // 确保历史记录加载完成后再发送问题
setTimeout(() => { setTimeout(() => {
handleSubmitQuestion(shouldSendQuestion) handleSubmitQuestion(shouldSendQuestion)
dispatch(clearShouldSendQuestion())
}, 100) }, 100)
} }
}, [shouldSendQuestion, isLoading]) }, [shouldSendQuestion, isLoading])
...@@ -216,7 +251,7 @@ export const Chat: React.FC = () => { ...@@ -216,7 +251,7 @@ export const Chat: React.FC = () => {
> >
<div className={styles.inter}> <div className={styles.inter}>
{allItems.map((record, index) => ( {allItems.map((record, index) => (
<div className="w-full chatItem mx-auto" key={`${record.role}-${index}-${record.question || record.answerList?.[0]?.answer || ''}`}> <div className="w-full chatItem mx-auto" key={`${record.role}-${record.id || index}-${record.question || record.answerList?.[0]?.answer || ''}`}>
{record.role === 'system' && <ChatWelcome />} {record.role === 'system' && <ChatWelcome />}
{record.role === 'user' && <ChatItemUser record={record} />} {record.role === 'user' && <ChatItemUser record={record} />}
{record.role === 'ai' && <ChatAnswerBox onSubmitQuestion={handleSubmitQuestion} isLastAnswer={index === allItems.length - 1} showIndex={0} record={record} index={index} />} {record.role === 'ai' && <ChatAnswerBox onSubmitQuestion={handleSubmitQuestion} isLastAnswer={index === allItems.length - 1} showIndex={0} record={record} index={index} />}
......
...@@ -98,7 +98,7 @@ export const Home: React.FC = () => { ...@@ -98,7 +98,7 @@ export const Home: React.FC = () => {
const initConversation = () => { const initConversation = () => {
const fromCollect = location.state?.fromCollect const fromCollect = location.state?.fromCollect
// 只有在访问首页时才创建新对话,如果已经在聊天页面则不创建 // 只有在访问首页时才创建新对话,如果已经在聊天页面则不创建
if (!fromCollect && location.pathname === '/') { if (!fromCollect && (location.pathname === '/' || location.pathname === '/home')) {
dispatch( dispatch(
createConversation({ createConversation({
conversationData: {}, conversationData: {},
...@@ -156,7 +156,7 @@ export const Home: React.FC = () => { ...@@ -156,7 +156,7 @@ export const Home: React.FC = () => {
initConversation() initConversation()
dispatch(fetchConversations()) dispatch(fetchConversations())
} }
}, [login, getQuestionList]) }, [token])
return ( return (
<div className={styles.homePage}> <div className={styles.homePage}>
......
...@@ -53,6 +53,7 @@ function getRandomIndices(total: number, count: number): number[] { ...@@ -53,6 +53,7 @@ function getRandomIndices(total: number, count: number): number[] {
const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({ checkAuth, questions, dotColor, background, title, iconImg, showRefresh = true, displayCount = 4 }) => { const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({ checkAuth, questions, dotColor, background, title, iconImg, showRefresh = true, displayCount = 4 }) => {
const [isRotating, setIsRotating] = useState(false) const [isRotating, setIsRotating] = useState(false)
const [displayedItems, setDisplayedItems] = useState<string[]>([]) const [displayedItems, setDisplayedItems] = useState<string[]>([])
const [isClicking, setIsClicking] = useState(false)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const updateDisplayedItems = useCallback(() => { const updateDisplayedItems = useCallback(() => {
...@@ -66,12 +67,17 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({ checkAu ...@@ -66,12 +67,17 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({ checkAu
setIsRotating(false) setIsRotating(false)
} }
const handleClick = (item: string) => { const handleClick = (item: string) => {
if (checkAuth()) { if (checkAuth() && !isClicking) {
setIsClicking(true)
dispatch(createConversation({ dispatch(createConversation({
conversationData: {}, conversationData: {},
shouldNavigate: true, shouldNavigate: true,
shouldSendQuestion: item, shouldSendQuestion: item,
})) }))
// 防止重复点击
setTimeout(() => {
setIsClicking(false)
}, 1000)
} }
} }
...@@ -122,7 +128,7 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({ checkAu ...@@ -122,7 +128,7 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({ checkAu
{ {
displayedItems.map((item, index) => ( displayedItems.map((item, index) => (
<motion.li <motion.li
key={`${item}-${index}`} key={`question-${item}`}
custom={index} custom={index}
variants={itemVariants} variants={itemVariants}
initial="hidden" initial="hidden"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment